This is an automated email from the ASF dual-hosted git repository. ephraimanierobi pushed a commit to branch v2-3-test in repository https://gitbox.apache.org/repos/asf/airflow.git
commit 447077904493975159a3b97975a6d615a21c3355 Author: Brent Bovenzi <[email protected]> AuthorDate: Mon May 2 19:38:57 2022 -0400 Improve react www tests (#23329) * Add shared test wrapper & treeData placeholder * remove console log * move testUtils to /utils * change unnamed export (cherry picked from commit 8f6b8551c325ff64f42080391a0f97b3a5d2e7d5) --- airflow/www/static/js/tree/Tree.jsx | 2 +- airflow/www/static/js/tree/api/useTreeData.js | 23 +++-- .../www/static/js/tree/api/useTreeData.test.jsx | 16 +--- airflow/www/static/js/tree/dagRuns/index.jsx | 2 +- airflow/www/static/js/tree/dagRuns/index.test.jsx | 99 ++++++---------------- airflow/www/static/js/tree/details/Header.jsx | 2 +- airflow/www/static/js/tree/details/content/Dag.jsx | 2 +- .../js/tree/details/content/dagRun/index.jsx | 2 +- .../js/tree/details/content/taskInstance/index.jsx | 2 +- airflow/www/static/js/tree/renderTaskRows.test.jsx | 33 +------- airflow/www/static/js/tree/utils/testUtils.jsx | 67 +++++++++++++++ 11 files changed, 121 insertions(+), 129 deletions(-) diff --git a/airflow/www/static/js/tree/Tree.jsx b/airflow/www/static/js/tree/Tree.jsx index 1283d14c1b..b2d1c1dfb1 100644 --- a/airflow/www/static/js/tree/Tree.jsx +++ b/airflow/www/static/js/tree/Tree.jsx @@ -47,7 +47,7 @@ const sidePanelKey = 'hideSidePanel'; const Tree = () => { const scrollRef = useRef(); const tableRef = useRef(); - const { data: { groups = {}, dagRuns = [] } } = useTreeData(); + const { data: { groups, dagRuns } } = useTreeData(); const { isRefreshOn, toggleRefresh, isPaused } = useAutoRefresh(); const isPanelOpen = localStorage.getItem(sidePanelKey) !== 'true'; const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: isPanelOpen }); diff --git a/airflow/www/static/js/tree/api/useTreeData.js b/airflow/www/static/js/tree/api/useTreeData.js index 9cb9737fd3..2a0468cac3 100644 --- a/airflow/www/static/js/tree/api/useTreeData.js +++ b/airflow/www/static/js/tree/api/useTreeData.js @@ -29,24 +29,30 @@ import useErrorToast from '../utils/useErrorToast'; // dagId comes from dag.html const dagId = getMetaValue('dag_id'); -const treeDataUrl = getMetaValue('tree_data_url'); +const treeDataUrl = getMetaValue('tree_data_url') || ''; const numRuns = getMetaValue('num_runs'); const urlRoot = getMetaValue('root'); const baseDate = getMetaValue('base_date'); +const emptyData = { + dagRuns: [], + groups: {}, +}; + const useTreeData = () => { - const emptyData = { - dagRuns: [], - groups: {}, - }; const initialData = formatData(treeData, emptyData); const { isRefreshOn, stopRefresh } = useAutoRefresh(); const errorToast = useErrorToast(); return useQuery('treeData', async () => { try { - const root = urlRoot ? `&root=${urlRoot}` : ''; - const base = baseDate ? `&base_date=${baseDate}` : ''; - const newData = await axios.get(`${treeDataUrl}?dag_id=${dagId}&num_runs=${numRuns}${root}${base}`); + const params = new URLSearchParams({ + dag_id: dagId, + }); + if (numRuns && numRuns !== 25) params.append('num_runs', numRuns); + if (urlRoot) params.append('root', urlRoot); + if (baseDate) params.append('base_date', baseDate); + + const newData = await axios.get(treeDataUrl, { params }); // turn off auto refresh if there are no active runs if (!areActiveRuns(newData.dagRuns)) stopRefresh(); return newData; @@ -62,6 +68,7 @@ const useTreeData = () => { // only refetch if the refresh switch is on refetchInterval: isRefreshOn && autoRefreshInterval * 1000, initialData, + placeholderData: emptyData, }); }; diff --git a/airflow/www/static/js/tree/api/useTreeData.test.jsx b/airflow/www/static/js/tree/api/useTreeData.test.jsx index 03d99ea7e0..01c59ef346 100644 --- a/airflow/www/static/js/tree/api/useTreeData.test.jsx +++ b/airflow/www/static/js/tree/api/useTreeData.test.jsx @@ -16,11 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -import React from 'react'; + import { renderHook } from '@testing-library/react-hooks'; -import { QueryClient, QueryClientProvider } from 'react-query'; import useTreeData from './useTreeData'; -import { AutoRefreshProvider } from '../context/autorefresh'; +import { Wrapper } from '../utils/testUtils'; /* global describe, test, expect, beforeAll */ @@ -41,17 +40,6 @@ const pendingTreeData = { ], }; -const Wrapper = ({ children }) => { - const queryClient = new QueryClient(); - return ( - <AutoRefreshProvider> - <QueryClientProvider client={queryClient}> - {children} - </QueryClientProvider> - </AutoRefreshProvider> - ); -}; - describe('Test useTreeData hook', () => { beforeAll(() => { global.autoRefreshInterval = 5; diff --git a/airflow/www/static/js/tree/dagRuns/index.jsx b/airflow/www/static/js/tree/dagRuns/index.jsx index c304b18bcf..462fffc8e3 100644 --- a/airflow/www/static/js/tree/dagRuns/index.jsx +++ b/airflow/www/static/js/tree/dagRuns/index.jsx @@ -38,7 +38,7 @@ const DurationTick = ({ children, ...rest }) => ( ); const DagRuns = () => { - const { data: { dagRuns = [] } } = useTreeData(); + const { data: { dagRuns } } = useTreeData(); const { selected, onSelect } = useSelection(); const durations = []; const runs = dagRuns.map((dagRun) => { diff --git a/airflow/www/static/js/tree/dagRuns/index.test.jsx b/airflow/www/static/js/tree/dagRuns/index.test.jsx index 15a30ce41a..550504b0fc 100644 --- a/airflow/www/static/js/tree/dagRuns/index.test.jsx +++ b/airflow/www/static/js/tree/dagRuns/index.test.jsx @@ -21,75 +21,43 @@ import React from 'react'; import { render } from '@testing-library/react'; -import { ChakraProvider, Table, Tbody } from '@chakra-ui/react'; import moment from 'moment-timezone'; -import { QueryClient, QueryClientProvider } from 'react-query'; -import { MemoryRouter } from 'react-router-dom'; import DagRuns from './index'; -import { ContainerRefProvider } from '../context/containerRef'; -import { TimezoneProvider } from '../context/timezone'; -import { AutoRefreshProvider } from '../context/autorefresh'; +import { TableWrapper } from '../utils/testUtils'; -global.moment = moment; - -const Wrapper = ({ children }) => { - const queryClient = new QueryClient(); - return ( - <React.StrictMode> - <ChakraProvider> - <QueryClientProvider client={queryClient}> - <ContainerRefProvider value={{}}> - <TimezoneProvider value={{ timezone: 'UTC' }}> - <AutoRefreshProvider value={{ isRefreshOn: false, stopRefresh: () => {} }}> - <MemoryRouter> - <Table> - <Tbody> - {children} - </Tbody> - </Table> - </MemoryRouter> - </AutoRefreshProvider> - </TimezoneProvider> - </ContainerRefProvider> - </QueryClientProvider> - </ChakraProvider> - </React.StrictMode> - ); -}; +const dagRuns = [ + { + dagId: 'dagId', + runId: 'run1', + dataIntervalStart: new Date(), + dataIntervalEnd: new Date(), + startDate: '2021-11-08T21:14:19.704433+00:00', + endDate: '2021-11-08T21:17:13.206426+00:00', + state: 'failed', + runType: 'scheduled', + executionDate: '2021-11-08T21:14:19.704433+00:00', + }, + { + dagId: 'dagId', + runId: 'run2', + dataIntervalStart: new Date(), + dataIntervalEnd: new Date(), + state: 'success', + runType: 'manual', + startDate: '2021-11-09T00:19:43.023200+00:00', + endDate: '2021-11-09T00:22:18.607167+00:00', + }, +]; describe('Test DagRuns', () => { - const dagRuns = [ - { - dagId: 'dagId', - runId: 'run1', - dataIntervalStart: new Date(), - dataIntervalEnd: new Date(), - startDate: '2021-11-08T21:14:19.704433+00:00', - endDate: '2021-11-08T21:17:13.206426+00:00', - state: 'failed', - runType: 'scheduled', - executionDate: '2021-11-08T21:14:19.704433+00:00', - }, - { - dagId: 'dagId', - runId: 'run2', - dataIntervalStart: new Date(), - dataIntervalEnd: new Date(), - state: 'success', - runType: 'manual', - startDate: '2021-11-09T00:19:43.023200+00:00', - endDate: '2021-11-09T00:22:18.607167+00:00', - }, - ]; - test('Durations and manual run arrow render correctly, but without any date ticks', () => { global.treeData = JSON.stringify({ groups: {}, dagRuns, }); const { queryAllByTestId, getByText, queryByText } = render( - <DagRuns />, { wrapper: Wrapper }, + <DagRuns />, { wrapper: TableWrapper }, ); expect(queryAllByTestId('run')).toHaveLength(2); expect(queryAllByTestId('manual-run')).toHaveLength(1); @@ -127,26 +95,15 @@ describe('Test DagRuns', () => { ], }); const { getByText } = render( - <DagRuns />, { wrapper: Wrapper }, + <DagRuns />, { wrapper: TableWrapper }, ); expect(getByText(moment.utc(dagRuns[0].executionDate).format('MMM DD, HH:mm'))).toBeInTheDocument(); }); test('Handles empty data correctly', () => { - global.treeData = { - groups: {}, - dagRuns: [], - }; - const { queryByTestId } = render( - <DagRuns />, { wrapper: Wrapper }, - ); - expect(queryByTestId('run')).toBeNull(); - }); - - test('Handles no data correctly', () => { - global.treeData = {}; + global.treeData = null; const { queryByTestId } = render( - <DagRuns />, { wrapper: Wrapper }, + <DagRuns />, { wrapper: TableWrapper }, ); expect(queryByTestId('run')).toBeNull(); }); diff --git a/airflow/www/static/js/tree/details/Header.jsx b/airflow/www/static/js/tree/details/Header.jsx index 5ce3b0583d..2bdaa47361 100644 --- a/airflow/www/static/js/tree/details/Header.jsx +++ b/airflow/www/static/js/tree/details/Header.jsx @@ -43,7 +43,7 @@ const LabelValue = ({ label, value }) => ( ); const Header = () => { - const { data: { dagRuns = [] } } = useTreeData(); + const { data: { dagRuns } } = useTreeData(); const { selected: { taskId, runId }, onSelect, clearSelection } = useSelection(); const { data: { tasks } } = useTasks(); const dagRun = dagRuns.find((r) => r.runId === runId); diff --git a/airflow/www/static/js/tree/details/content/Dag.jsx b/airflow/www/static/js/tree/details/content/Dag.jsx index 928b4f3f55..511844d72e 100644 --- a/airflow/www/static/js/tree/details/content/Dag.jsx +++ b/airflow/www/static/js/tree/details/content/Dag.jsx @@ -42,7 +42,7 @@ const dagDetailsUrl = getMetaValue('dag_details_url'); const Dag = () => { const { data: taskData } = useTasks(dagId); - const { data: { dagRuns = [] } } = useTreeData(); + const { data: { dagRuns } } = useTreeData(); if (!taskData) return null; const { tasks = [], totalEntries = '' } = taskData; diff --git a/airflow/www/static/js/tree/details/content/dagRun/index.jsx b/airflow/www/static/js/tree/details/content/dagRun/index.jsx index 1816385c58..bd096f1510 100644 --- a/airflow/www/static/js/tree/details/content/dagRun/index.jsx +++ b/airflow/www/static/js/tree/details/content/dagRun/index.jsx @@ -43,7 +43,7 @@ const graphUrl = getMetaValue('graph_url'); const dagRunDetailsUrl = getMetaValue('dagrun_details_url'); const DagRun = ({ runId }) => { - const { data: { dagRuns = [] } } = useTreeData(); + const { data: { dagRuns } } = useTreeData(); const run = dagRuns.find((dr) => dr.runId === runId); if (!run) return null; const { diff --git a/airflow/www/static/js/tree/details/content/taskInstance/index.jsx b/airflow/www/static/js/tree/details/content/taskInstance/index.jsx index 0e4f441e24..e40471f071 100644 --- a/airflow/www/static/js/tree/details/content/taskInstance/index.jsx +++ b/airflow/www/static/js/tree/details/content/taskInstance/index.jsx @@ -56,7 +56,7 @@ const getTask = ({ taskId, runId, task }) => { const TaskInstance = ({ taskId, runId }) => { const [selectedRows, setSelectedRows] = useState([]); - const { data: { groups = {}, dagRuns = [] } } = useTreeData(); + const { data: { groups, dagRuns } } = useTreeData(); const group = getTask({ taskId, runId, task: groups }); const run = dagRuns.find((r) => r.runId === runId); const { executionDate } = run; diff --git a/airflow/www/static/js/tree/renderTaskRows.test.jsx b/airflow/www/static/js/tree/renderTaskRows.test.jsx index 88afb4a580..415f18b89d 100644 --- a/airflow/www/static/js/tree/renderTaskRows.test.jsx +++ b/airflow/www/static/js/tree/renderTaskRows.test.jsx @@ -21,15 +21,9 @@ import React from 'react'; import { render, fireEvent } from '@testing-library/react'; -import { ChakraProvider, Table, Tbody } from '@chakra-ui/react'; -import moment from 'moment'; -import { QueryClient, QueryClientProvider } from 'react-query'; -import { MemoryRouter } from 'react-router-dom'; import renderTaskRows from './renderTaskRows'; -import { ContainerRefProvider } from './context/containerRef'; - -global.moment = moment; +import { TableWrapper } from './utils/testUtils'; const mockTreeData = { groups: { @@ -94,27 +88,6 @@ const mockTreeData = { ], }; -const Wrapper = ({ children }) => { - const queryClient = new QueryClient(); - return ( - <React.StrictMode> - <ChakraProvider> - <QueryClientProvider client={queryClient}> - <ContainerRefProvider value={{}}> - <MemoryRouter> - <Table> - <Tbody> - {children} - </Tbody> - </Table> - </MemoryRouter> - </ContainerRefProvider> - </QueryClientProvider> - </ChakraProvider> - </React.StrictMode> - ); -}; - describe('Test renderTaskRows', () => { test('Group defaults to closed but clicking on the name will open a group', () => { global.treeData = mockTreeData; @@ -123,7 +96,7 @@ describe('Test renderTaskRows', () => { const { getByTestId, getByText, getAllByTestId } = render( <>{renderTaskRows({ task, dagRunIds })}</>, - { wrapper: Wrapper }, + { wrapper: TableWrapper }, ); const groupName = getByText('group_1'); @@ -168,7 +141,7 @@ describe('Test renderTaskRows', () => { const { queryByTestId, getByText } = render( <>{renderTaskRows({ task, dagRunIds: [] })}</>, - { wrapper: Wrapper }, + { wrapper: TableWrapper }, ); expect(getByText('group_1')).toBeInTheDocument(); diff --git a/airflow/www/static/js/tree/utils/testUtils.jsx b/airflow/www/static/js/tree/utils/testUtils.jsx new file mode 100644 index 0000000000..946fe0dd88 --- /dev/null +++ b/airflow/www/static/js/tree/utils/testUtils.jsx @@ -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 from 'react'; +import { ChakraProvider, Table, Tbody } from '@chakra-ui/react'; +import { QueryClient, QueryClientProvider } from 'react-query'; +import { MemoryRouter } from 'react-router-dom'; +// eslint-disable-next-line import/no-extraneous-dependencies +import moment from 'moment-timezone'; + +import { ContainerRefProvider } from '../context/containerRef'; +import { TimezoneProvider } from '../context/timezone'; +import { AutoRefreshProvider } from '../context/autorefresh'; + +global.moment = moment; + +export const Wrapper = ({ children }) => { + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + cacheTime: Infinity, + staleTime: Infinity, + }, + }, + }); + return ( + <ChakraProvider> + <QueryClientProvider client={queryClient}> + <ContainerRefProvider> + <TimezoneProvider> + <AutoRefreshProvider> + <MemoryRouter> + {children} + </MemoryRouter> + </AutoRefreshProvider> + </TimezoneProvider> + </ContainerRefProvider> + </QueryClientProvider> + </ChakraProvider> + ); +}; + +export const TableWrapper = ({ children }) => ( + <Wrapper> + <Table> + <Tbody> + {children} + </Tbody> + </Table> + </Wrapper> +);
