diff --git a/airflow/www/static/js/grid/Main.jsx b/airflow/www/static/js/grid/Main.jsx
index 98aa360aa6789..5e9a4f2a9a12d 100644
--- a/airflow/www/static/js/grid/Main.jsx
+++ b/airflow/www/static/js/grid/Main.jsx
@@ -25,17 +25,21 @@ import {
Flex,
useDisclosure,
Divider,
+ Spinner,
} from '@chakra-ui/react';
+import { isEmpty } from 'lodash';
import Details from './details';
import useSelection from './utils/useSelection';
import Grid from './Grid';
import FilterBar from './FilterBar';
import LegendRow from './LegendRow';
+import { useGridData } from './api';
const detailsPanelKey = 'hideDetailsPanel';
const Main = () => {
+ const { data: { groups }, isLoading } = useGridData();
const isPanelOpen = localStorage.getItem(detailsPanelKey) !== 'true';
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: isPanelOpen });
const { clearSelection } = useSelection();
@@ -57,14 +61,20 @@ const Main = () => {
-
-
- {isOpen && ( )}
-
+ {isLoading || isEmpty(groups)
+ ? ()
+ : (
+ <>
+
+
+ {isOpen && ( )}
+
+ >
+ )}
);
diff --git a/airflow/www/static/js/grid/api/useGridData.js b/airflow/www/static/js/grid/api/useGridData.js
index d31712989bcfd..38d4e00748d32 100644
--- a/airflow/www/static/js/grid/api/useGridData.js
+++ b/airflow/www/static/js/grid/api/useGridData.js
@@ -17,14 +17,13 @@
* under the License.
*/
-/* global autoRefreshInterval, gridData */
+/* global autoRefreshInterval */
import { useQuery } from 'react-query';
import axios from 'axios';
import { getMetaValue } from '../../utils';
import { useAutoRefresh } from '../context/autorefresh';
-import { areActiveRuns, formatData } from '../utils/gridData';
import useErrorToast from '../utils/useErrorToast';
import useFilters, {
BASE_DATE_PARAM, NUM_RUNS_PARAM, RUN_STATE_PARAM, RUN_TYPE_PARAM, now,
@@ -42,8 +41,9 @@ const emptyData = {
groups: {},
};
+export const areActiveRuns = (runs = []) => runs.filter((run) => ['queued', 'running', 'scheduled'].includes(run.state)).length > 0;
+
const useGridData = () => {
- const initialData = formatData(gridData, emptyData);
const { isRefreshOn, stopRefresh } = useAutoRefresh();
const errorToast = useErrorToast();
const {
@@ -75,7 +75,6 @@ const useGridData = () => {
throw (error);
}
}, {
- initialData,
placeholderData: emptyData,
// only refetch if the refresh switch is on
refetchInterval: isRefreshOn && autoRefreshInterval * 1000,
diff --git a/airflow/www/static/js/grid/utils/gridData.test.js b/airflow/www/static/js/grid/api/useGridData.test.js
similarity index 96%
rename from airflow/www/static/js/grid/utils/gridData.test.js
rename to airflow/www/static/js/grid/api/useGridData.test.js
index 6bbd8bf8b6a6d..29a7f1ac8a11f 100644
--- a/airflow/www/static/js/grid/utils/gridData.test.js
+++ b/airflow/www/static/js/grid/api/useGridData.test.js
@@ -19,7 +19,7 @@
/* global describe, test, expect */
-import { areActiveRuns } from './gridData';
+import { areActiveRuns } from './useGridData';
describe('Test areActiveRuns()', () => {
test('Correctly detects active runs', () => {
diff --git a/airflow/www/static/js/grid/api/useGridData.test.jsx b/airflow/www/static/js/grid/api/useGridData.test.jsx
deleted file mode 100644
index 24aece6f5919f..0000000000000
--- a/airflow/www/static/js/grid/api/useGridData.test.jsx
+++ /dev/null
@@ -1,78 +0,0 @@
-/*!
- * 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.
- */
-
-/* global describe, test, expect, beforeAll */
-
-import { renderHook } from '@testing-library/react-hooks';
-import useGridData from './useGridData';
-import { Wrapper } from '../utils/testUtils';
-
-const pendingGridData = {
- groups: {},
- dag_runs: [
- {
- dag_id: 'example_python_operator',
- run_id: 'manual__2021-11-08T21:14:17.170046+00:00',
- start_date: null,
- end_date: null,
- state: 'queued',
- execution_date: '2021-11-08T21:14:17.170046+00:00',
- data_interval_start: '2021-11-08T21:14:17.170046+00:00',
- data_interval_end: '2021-11-08T21:14:17.170046+00:00',
- run_type: 'manual',
- },
- ],
-};
-
-describe('Test useGridData hook', () => {
- beforeAll(() => {
- global.autoRefreshInterval = 5;
- });
-
- test('data is valid camelcase json', () => {
- global.gridData = JSON.stringify(pendingGridData);
-
- const { result } = renderHook(() => useGridData(), { wrapper: Wrapper });
- const { data } = result.current;
-
- expect(typeof data === 'object').toBe(true);
- expect(data.dagRuns).toBeDefined();
- expect(data.dag_runs).toBeUndefined();
- });
-
- test('Can handle no gridData', () => {
- global.gridData = null;
-
- const { result } = renderHook(() => useGridData(), { wrapper: Wrapper });
- const { data } = result.current;
-
- expect(data.dagRuns).toStrictEqual([]);
- expect(data.groups).toStrictEqual({});
- });
-
- test('Can handle empty gridData object', () => {
- global.gridData = {};
-
- const { result } = renderHook(() => useGridData(), { wrapper: Wrapper });
- const { data } = result.current;
-
- expect(data.dagRuns).toStrictEqual([]);
- expect(data.groups).toStrictEqual({});
- });
-});
diff --git a/airflow/www/static/js/grid/api/useTasks.js b/airflow/www/static/js/grid/api/useTasks.js
index 9d0f56d7cb59c..a635622e2787b 100644
--- a/airflow/www/static/js/grid/api/useTasks.js
+++ b/airflow/www/static/js/grid/api/useTasks.js
@@ -23,9 +23,9 @@ import { getMetaValue } from '../../utils';
const tasksUrl = getMetaValue('tasks_api');
-export default function useTasks(dagId) {
+export default function useTasks() {
return useQuery(
- ['tasks', dagId],
+ 'tasks',
() => axios.get(tasksUrl),
{
placeholderData: { tasks: [] },
diff --git a/airflow/www/static/js/grid/context/autorefresh.jsx b/airflow/www/static/js/grid/context/autorefresh.jsx
index ae242e5bf66aa..7fbdc405f93c1 100644
--- a/airflow/www/static/js/grid/context/autorefresh.jsx
+++ b/airflow/www/static/js/grid/context/autorefresh.jsx
@@ -17,11 +17,10 @@
* under the License.
*/
-/* global localStorage, gridData, document */
+/* global localStorage, document */
import React, { useContext, useState, useEffect } from 'react';
import { getMetaValue } from '../../utils';
-import { formatData, areActiveRuns } from '../utils/gridData';
const autoRefreshKey = 'disabledAutoRefresh';
@@ -31,17 +30,9 @@ const isRefreshDisabled = JSON.parse(localStorage.getItem(autoRefreshKey));
const AutoRefreshContext = React.createContext(null);
export const AutoRefreshProvider = ({ children }) => {
- let dagRuns = [];
- try {
- const data = JSON.parse(gridData);
- if (data.dag_runs) dagRuns = formatData(data.dag_runs);
- } catch {
- dagRuns = [];
- }
const [isPaused, setIsPaused] = useState(initialIsPaused);
- const isActive = areActiveRuns(dagRuns);
const isRefreshAllowed = !(isPaused || isRefreshDisabled);
- const initialState = isRefreshAllowed && isActive;
+ const initialState = isRefreshAllowed;
const [isRefreshOn, setRefresh] = useState(initialState);
@@ -67,8 +58,6 @@ export const AutoRefreshProvider = ({ children }) => {
setIsPaused(!e.value);
if (!e.value) {
stopRefresh();
- } else if (isActive) {
- setRefresh(true);
}
};
diff --git a/airflow/www/static/js/grid/dagRuns/index.test.jsx b/airflow/www/static/js/grid/dagRuns/index.test.jsx
index a507df522c565..0420d7aba8792 100644
--- a/airflow/www/static/js/grid/dagRuns/index.test.jsx
+++ b/airflow/www/static/js/grid/dagRuns/index.test.jsx
@@ -112,15 +112,7 @@ describe('Test DagRuns', () => {
});
test('Handles empty data correctly', () => {
- global.gridData = null;
- const { queryByTestId } = render(
- , { wrapper: TableWrapper },
- );
- expect(queryByTestId('run')).toBeNull();
- });
-
- test('Handles no data correctly', () => {
- global.gridData = {};
+ global.autoRefreshInterval = 0;
const { queryByTestId } = render(
, { wrapper: TableWrapper },
);
diff --git a/airflow/www/static/js/grid/details/content/Dag.jsx b/airflow/www/static/js/grid/details/content/Dag.jsx
index 0f434d88fd72f..e527b494cf283 100644
--- a/airflow/www/static/js/grid/details/content/Dag.jsx
+++ b/airflow/www/static/js/grid/details/content/Dag.jsx
@@ -37,11 +37,10 @@ import { useTasks, useGridData } from '../../api';
import Time from '../../components/Time';
import { SimpleStatus } from '../../components/StatusBox';
-const dagId = getMetaValue('dag_id');
const dagDetailsUrl = getMetaValue('dag_details_url');
const Dag = () => {
- const { data: taskData } = useTasks(dagId);
+ const { data: taskData } = useTasks();
const { data: { dagRuns } } = useGridData();
if (!taskData) return null;
const { tasks = [], totalEntries = '' } = taskData;
diff --git a/airflow/www/static/js/grid/index.jsx b/airflow/www/static/js/grid/index.jsx
index 3d8702841edc6..b8c6db5324d4b 100644
--- a/airflow/www/static/js/grid/index.jsx
+++ b/airflow/www/static/js/grid/index.jsx
@@ -48,6 +48,7 @@ const queryClient = new QueryClient({
refetchOnWindowFocus: false,
retry: 1,
retryDelay: 500,
+ staleTime: 5 * 60 * 1000, // 5 minutes
refetchOnMount: true, // Refetches stale queries, not "always"
},
mutations: {
diff --git a/airflow/www/static/js/grid/utils/gridData.js b/airflow/www/static/js/grid/utils/gridData.js
deleted file mode 100644
index 95171652b7a7b..0000000000000
--- a/airflow/www/static/js/grid/utils/gridData.js
+++ /dev/null
@@ -1,34 +0,0 @@
-/*!
- * 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 camelcaseKeys from 'camelcase-keys';
-
-export const areActiveRuns = (runs = []) => runs.filter((run) => ['queued', 'running', 'scheduled'].includes(run.state)).length > 0;
-
-export const formatData = (data, emptyData) => {
- if (!data || !Object.keys(data).length) {
- return emptyData;
- }
- let formattedData = data;
- // Convert to json if needed
- if (typeof data === 'string') formattedData = JSON.parse(data);
- // change from pascal to camelcase
- formattedData = camelcaseKeys(formattedData, { deep: true });
- return formattedData;
-};
diff --git a/airflow/www/templates/airflow/grid.html b/airflow/www/templates/airflow/grid.html
index 46d52f7d27f58..f72e0ed4f1704 100644
--- a/airflow/www/templates/airflow/grid.html
+++ b/airflow/www/templates/airflow/grid.html
@@ -39,7 +39,6 @@
{% block tail_js %}
{{ super()}}