Skip to content

Commit fd4e344

Browse files
authored
Fix useTasks crash on error (apache#24152)
* Prevent UI from crashing on Get API error * add test * don't show API errors in test logs * use setMinutes inline
1 parent 80c1ce7 commit fd4e344

File tree

8 files changed

+142
-8
lines changed

8 files changed

+142
-8
lines changed

airflow/www/jest-setup.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,21 @@
2020
*/
2121

2222
import '@testing-library/jest-dom';
23+
import axios from 'axios';
24+
import { setLogger } from 'react-query';
25+
26+
axios.defaults.adapter = require('axios/lib/adapters/http');
27+
28+
axios.interceptors.response.use(
29+
(res) => res.data || res,
30+
);
31+
32+
setLogger({
33+
log: console.log,
34+
warn: console.warn,
35+
// ✅ no more errors on the console
36+
error: () => {},
37+
});
2338

2439
// Mock global objects we use across the app
2540
global.stateColors = {

airflow/www/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
"mini-css-extract-plugin": "1.6.0",
5959
"moment": "^2.29.2",
6060
"moment-locales-webpack-plugin": "^1.2.0",
61+
"nock": "^13.2.4",
6162
"optimize-css-assets-webpack-plugin": "6.0.0",
6263
"style-loader": "^1.2.1",
6364
"stylelint": "^13.6.1",

airflow/www/static/js/grid/api/useMappedInstances.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@ export default function useMappedInstances({
4040
}),
4141
{
4242
keepPreviousData: true,
43+
initialData: { taskInstances: [], totalEntries: 0 },
4344
refetchInterval: isRefreshOn && autoRefreshInterval * 1000,
45+
// staleTime should be similar to the refresh interval
46+
staleTime: autoRefreshInterval * 1000,
4447
},
4548
);
4649
}

airflow/www/static/js/grid/api/useTasks.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,15 @@ import axios from 'axios';
2121
import { useQuery } from 'react-query';
2222
import { getMetaValue } from '../../utils';
2323

24-
const tasksUrl = getMetaValue('tasks_api');
25-
2624
export default function useTasks() {
2725
return useQuery(
2826
'tasks',
29-
() => axios.get(tasksUrl),
27+
() => {
28+
const tasksUrl = getMetaValue('tasks_api');
29+
return axios.get(tasksUrl);
30+
},
3031
{
31-
placeholderData: { tasks: [] },
32+
initialData: { tasks: [], totalEntries: 0 },
3233
},
3334
);
3435
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*!
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
/* global describe, test, expect, beforeEach, afterEach, jest */
21+
22+
import React from 'react';
23+
import { renderHook } from '@testing-library/react-hooks';
24+
import { QueryClient, QueryClientProvider } from 'react-query';
25+
import nock from 'nock';
26+
27+
import useTasks from './useTasks';
28+
import * as metaUtils from '../../utils';
29+
30+
const Wrapper = ({ children }) => {
31+
const queryClient = new QueryClient({
32+
defaultOptions: {
33+
queries: {
34+
retry: 0,
35+
},
36+
},
37+
});
38+
return (
39+
<QueryClientProvider client={queryClient}>
40+
{children}
41+
</QueryClientProvider>
42+
);
43+
};
44+
45+
const fakeUrl = 'http://fake.api';
46+
47+
describe('Test useTasks hook', () => {
48+
let spy;
49+
beforeEach(() => {
50+
spy = jest.spyOn(metaUtils, 'getMetaValue').mockReturnValue(`${fakeUrl}`);
51+
});
52+
53+
afterEach(() => {
54+
spy.mockRestore();
55+
nock.cleanAll();
56+
});
57+
58+
test('initialData works normally', async () => {
59+
const scope = nock(fakeUrl)
60+
.get('/')
61+
.reply(200, { totalEntries: 1, tasks: [{ taskId: 'task_id' }] });
62+
const { result, waitFor } = renderHook(() => useTasks(), { wrapper: Wrapper });
63+
64+
expect(result.current.data.totalEntries).toBe(0);
65+
66+
await waitFor(() => result.current.isFetching);
67+
68+
expect(result.current.data.totalEntries).toBe(0);
69+
70+
await waitFor(() => !result.current.isFetching);
71+
72+
expect(result.current.data.totalEntries).toBe(1);
73+
scope.done();
74+
});
75+
76+
test('initialData persists even if there is an error', async () => {
77+
const scope = nock(fakeUrl)
78+
.get('/')
79+
.replyWithError('something awful happened');
80+
const { result, waitFor } = renderHook(() => useTasks(), { wrapper: Wrapper });
81+
82+
expect(result.current.data.totalEntries).toBe(0);
83+
84+
await waitFor(() => result.current.isError);
85+
86+
expect(result.current.data.totalEntries).toBe(0);
87+
scope.done();
88+
});
89+
});

airflow/www/static/js/grid/details/content/Dag.jsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,8 @@ import { SimpleStatus } from '../../components/StatusBox';
4040
const dagDetailsUrl = getMetaValue('dag_details_url');
4141

4242
const Dag = () => {
43-
const { data: taskData } = useTasks();
43+
const { data: { tasks, totalEntries } } = useTasks();
4444
const { data: { dagRuns } } = useGridData();
45-
if (!taskData) return null;
46-
const { tasks = [], totalEntries = '' } = taskData;
4745

4846
// Build a key/value object of operator counts, the name is hidden inside of t.classRef.className
4947
const operators = {};

airflow/www/static/js/grid/index.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,13 @@ shadowRoot.appendChild(mainElement);
4545
const queryClient = new QueryClient({
4646
defaultOptions: {
4747
queries: {
48+
notifyOnChangeProps: 'tracked',
4849
refetchOnWindowFocus: false,
4950
retry: 1,
5051
retryDelay: 500,
51-
staleTime: 5 * 60 * 1000, // 5 minutes
5252
refetchOnMount: true, // Refetches stale queries, not "always"
53+
staleTime: 5 * 60 * 1000, // 5 minutes
54+
initialDataUpdatedAt: new Date().setMinutes(-6), // make sure initial data is already expired
5355
},
5456
mutations: {
5557
retry: 1,

airflow/www/yarn.lock

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7794,6 +7794,11 @@ json-stable-stringify-without-jsonify@^1.0.1:
77947794
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
77957795
integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=
77967796

7797+
json-stringify-safe@^5.0.1:
7798+
version "5.0.1"
7799+
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
7800+
integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==
7801+
77977802
json5@^0.5.0, json5@^0.5.1:
77987803
version "0.5.1"
77997804
resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
@@ -8009,6 +8014,11 @@ [email protected]:
80098014
resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz#617121f89ac55f59047c7aec1ccd6654c6590f55"
80108015
integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==
80118016

8017+
lodash.set@^4.3.2:
8018+
version "4.3.2"
8019+
resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23"
8020+
integrity sha512-4hNPN5jlm/N/HLMCO43v8BXKq9Z7QdAGc/VGrRD61w8gN9g/6jF9A4L1pbUgBLCffi0w9VsXfTOij5x8iTyFvg==
8021+
80128022
lodash.truncate@^4.4.2:
80138023
version "4.4.2"
80148024
resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
@@ -8543,6 +8553,16 @@ nice-try@^1.0.4:
85438553
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
85448554
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
85458555

8556+
nock@^13.2.4:
8557+
version "13.2.4"
8558+
resolved "https://registry.yarnpkg.com/nock/-/nock-13.2.4.tgz#43a309d93143ee5cdcca91358614e7bde56d20e1"
8559+
integrity sha512-8GPznwxcPNCH/h8B+XZcKjYPXnUV5clOKCjAqyjsiqA++MpNx9E9+t8YPp0MbThO+KauRo7aZJ1WuIZmOrT2Ug==
8560+
dependencies:
8561+
debug "^4.1.0"
8562+
json-stringify-safe "^5.0.1"
8563+
lodash.set "^4.3.2"
8564+
propagate "^2.0.0"
8565+
85468566
node-domexception@^1.0.0:
85478567
version "1.0.0"
85488568
resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5"
@@ -9658,6 +9678,11 @@ prop-types@^15.6.2, prop-types@^15.7.2:
96589678
object-assign "^4.1.1"
96599679
react-is "^16.13.1"
96609680

9681+
propagate@^2.0.0:
9682+
version "2.0.1"
9683+
resolved "https://registry.yarnpkg.com/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45"
9684+
integrity sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==
9685+
96619686
prr@~1.0.1:
96629687
version "1.0.1"
96639688
resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"

0 commit comments

Comments
 (0)