Skip to content

Commit

Permalink
add timezone context in new ui (#15096)
Browse files Browse the repository at this point in the history
* add a timezone provider for new ui

* /providers directory, tests and linting

* remove config check
  • Loading branch information
bbovenzi authored Mar 31, 2021
1 parent a4aee3f commit 88a7444
Show file tree
Hide file tree
Showing 15 changed files with 118 additions and 15 deletions.
2 changes: 1 addition & 1 deletion airflow/ui/.neutrinorc.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ module.exports = {
neutrino.config.resolve.alias.set('src', resolve(__dirname, 'src'));
neutrino.config.resolve.alias.set('views', resolve(__dirname, 'src/views'));
neutrino.config.resolve.alias.set('utils', resolve(__dirname, 'src/utils'));
neutrino.config.resolve.alias.set('auth', resolve(__dirname, 'src/auth'));
neutrino.config.resolve.alias.set('providers', resolve(__dirname, 'src/providers'));
neutrino.config.resolve.alias.set('components', resolve(__dirname, 'src/components'));
neutrino.config.resolve.alias.set('interfaces', resolve(__dirname, 'src/interfaces'));
neutrino.config.resolve.alias.set('api', resolve(__dirname, 'src/api'));
Expand Down
2 changes: 1 addition & 1 deletion airflow/ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { hot } from 'react-hot-loader';
import React from 'react';
import { Route, Redirect, Switch } from 'react-router-dom';

import PrivateRoute from 'auth/PrivateRoute';
import PrivateRoute from 'providers/auth/PrivateRoute';

import Pipelines from 'views/Pipelines';

Expand Down
2 changes: 2 additions & 0 deletions airflow/ui/src/api/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,5 @@ export const defaultDags = { dags: [], totalEntries: 0 };
export const defaultDagRuns = { dagRuns: [], totalEntries: 0 };

export const defaultTaskInstances = { taskInstances: [], totalEntries: 0 };

export const defaultConfig = { sections: [] };
8 changes: 7 additions & 1 deletion airflow/ui/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ import {
import humps from 'humps';
import { useToast } from '@chakra-ui/react';

import type { Dag, DagRun, Version } from 'interfaces';
import type {
Config, Dag, DagRun, Version,
} from 'interfaces';
import type {
DagsResponse,
DagRunsResponse,
Expand Down Expand Up @@ -85,6 +87,10 @@ export function useVersion() {
);
}

export function useConfig() {
return useQuery<Config, Error>('config', (): Promise<Config> => axios.get('/config'));
}

export function useTriggerRun(dagId: Dag['dagId']) {
const queryClient = useQueryClient();
const toast = useToast();
Expand Down
9 changes: 6 additions & 3 deletions airflow/ui/src/components/AppContainer/AppHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

import React from 'react';
import { Link } from 'react-router-dom';
import dayjs from 'dayjs';
import {
Avatar,
Box,
Expand All @@ -41,11 +40,15 @@ import {
MdAccountCircle,
MdExitToApp,
} from 'react-icons/md';
import dayjs from 'dayjs';
import timezone from 'dayjs/plugin/timezone';

import { useAuthContext } from 'auth/context';
import { useAuthContext } from 'providers/auth/context';

import ApacheAirflowLogo from 'components/icons/ApacheAirflowLogo';

dayjs.extend(timezone);

interface Props {
bodyBg: string;
overlayBg: string;
Expand All @@ -54,7 +57,7 @@ interface Props {

const AppHeader: React.FC<Props> = ({ bodyBg, overlayBg, breadcrumb }) => {
const { toggleColorMode } = useColorMode();
const now = dayjs();
const now = dayjs().tz();
const headerHeight = '56px';
const { hasValidAuthToken, logout } = useAuthContext();
const darkLightIcon = useColorModeValue(MdBrightness2, MdWbSunny);
Expand Down
2 changes: 1 addition & 1 deletion airflow/ui/src/components/AppContainer/AppNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
FiUsers,
} from 'react-icons/fi';

import { useAuthContext } from 'auth/context';
import { useAuthContext } from 'providers/auth/context';

import PinwheelLogo from 'components/icons/PinwheelLogo';
import PipelineIcon from 'components/icons/PipelineIcon';
Expand Down
2 changes: 1 addition & 1 deletion airflow/ui/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
QueryClientProvider,
} from 'react-query';

import AuthProvider from 'auth/AuthProvider';
import AuthProvider from 'providers/auth/AuthProvider';

import App from './App';
import theme from './theme';
Expand Down
9 changes: 9 additions & 0 deletions airflow/ui/src/interfaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,12 @@ export interface Version {
version: string,
gitVersion: string,
}

export interface ConfigSection {
name: string;
options: Record<string, string>[];
}

export interface Config {
sections: ConfigSection[];
}
67 changes: 67 additions & 0 deletions airflow/ui/src/providers/TimezoneProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*!
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import React, {
createContext, useContext, useState, ReactNode, ReactElement, useEffect,
} from 'react';
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import dayjsTz from 'dayjs/plugin/timezone';

dayjs.extend(utc);
dayjs.extend(dayjsTz);

interface TimezoneContextData {
timezone: string;
setTimezone: (value: string) => void;
}

export const TimezoneContext = createContext<TimezoneContextData>({
timezone: 'UTC',
setTimezone: () => {},
});

export const useTimezoneContext = () => useContext(TimezoneContext);

type Props = {
children: ReactNode;
};

const TimezoneProvider = ({ children }: Props): ReactElement => {
// TODO: add in default_timezone when GET /ui-metadata is available
// guess timezone on browser or default to utc
const [timezone, setTimezone] = useState(dayjs.tz.guess() || 'UTC');

useEffect(() => {
dayjs.tz.setDefault(timezone);
}, [timezone]);

return (
<TimezoneContext.Provider
value={{
timezone,
setTimezone,
}}
>
{children}
</TimezoneContext.Provider>
);
};

export default TimezoneProvider;
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const AuthProvider = ({ children }: Props): ReactElement => {
return Promise.reject(err);
},
);
axios.defaults.headers.common.Accept = 'application/json';

useEffect(() => {
const token = get('token');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ import React, {
import { Route, RouteProps } from 'react-router-dom';

import Login from 'views/Login';
// TimezoneProvider has to be used after authentication
import TimezoneProvider from 'providers/TimezoneProvider';
import { useAuthContext } from './context';

const PrivateRoute: FC<RouteProps> = (props) => {
const { hasValidAuthToken } = useAuthContext();
return hasValidAuthToken ? <Route {...props} /> : <Login />;
return hasValidAuthToken ? <TimezoneProvider><Route {...props} /></TimezoneProvider> : <Login />;
};

export default PrivateRoute;
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,12 @@ interface AuthContextData {
error: Error | null;
}

export const authContextDefaultValue: AuthContextData = {
export const AuthContext = createContext<AuthContextData>({
hasValidAuthToken: false,
login: () => null,
logout: () => null,
loading: true,
error: null,
};

export const AuthContext = createContext<AuthContextData>(authContextDefaultValue);
});

export const useAuthContext = () => useContext(AuthContext);
2 changes: 1 addition & 1 deletion airflow/ui/src/views/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { FiLock, FiUser } from 'react-icons/fi';

import AppContainer from 'components/AppContainer';

import { useAuthContext } from 'auth/context';
import { useAuthContext } from 'providers/auth/context';

const Login: React.FC = () => {
const [username, setUsername] = useState('');
Expand Down
8 changes: 7 additions & 1 deletion airflow/ui/test/Login.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,23 @@ import { render, fireEvent, waitFor } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
import nock from 'nock';
import axios from 'axios';
import dayjs from 'dayjs';
import timezone from 'dayjs/plugin/timezone';
import utc from 'dayjs/plugin/utc';

import Login from 'views/Login';
import App from 'App';
import AuthProvider from 'auth/AuthProvider';
import AuthProvider from 'providers/auth/AuthProvider';

import { url, defaultHeaders, QueryWrapper } from './utils';

dayjs.extend(utc);
dayjs.extend(timezone);
axios.defaults.adapter = require('axios/lib/adapters/http');

describe('test login component', () => {
beforeAll(() => {
dayjs.tz.setDefault('UTC');
nock(url)
.defaultReplyHeaders(defaultHeaders)
.persist()
Expand Down
9 changes: 9 additions & 0 deletions airflow/ui/test/Pipelines.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,17 @@ import '@testing-library/jest-dom';
import { render, waitFor, fireEvent } from '@testing-library/react';
import nock from 'nock';
import axios from 'axios';
import dayjs from 'dayjs';
import timezone from 'dayjs/plugin/timezone';
import utc from 'dayjs/plugin/utc';

import Pipelines from 'views/Pipelines';
import {
defaultHeaders, QueryWrapper, RouterWrapper, url,
} from './utils';

dayjs.extend(utc);
dayjs.extend(timezone);
axios.defaults.adapter = require('axios/lib/adapters/http');

const sampleDag = {
Expand All @@ -49,6 +54,10 @@ const sampleDag = {
};

describe('Test Pipelines Table', () => {
beforeAll(() => {
dayjs.tz.setDefault('UTC');
});

beforeEach(() => {
nock(url)
.defaultReplyHeaders(defaultHeaders)
Expand Down

0 comments on commit 88a7444

Please sign in to comment.