Skip to content

Commit 3f454f9

Browse files
authored
Add status to card loading (#57)
* Add status to card loading * Fix linting errors
1 parent 22acc45 commit 3f454f9

File tree

8 files changed

+66
-16
lines changed

8 files changed

+66
-16
lines changed

src/components/MFCard/__tests__/useTaskCards.test.cypress.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ import { Task } from '../../../types';
77
import { Decorator } from '../../DAG/DAGUtils';
88

99
const MockComponent: React.FC<{ task?: Task | null; decos?: Decorator[] }> = ({ task = null, decos = [] }) => {
10-
const cards = useTaskCards(task, decos);
10+
const cardsResult = useTaskCards(task, decos);
1111

1212
return (
13-
<div data-testid="cardslist">
14-
{cards.map((value) => (
13+
<div data-testid="cardslist" className={cardsResult.status}>
14+
{cardsResult.cards.map((value) => (
1515
<div key={value.hash} data-testid="card">{JSON.stringify(value)}</div>
1616
))}
1717
</div>
@@ -51,12 +51,14 @@ describe('useTaskCards', () => {
5151
{ name: 'card', attributes: {}, statically_defined: false }
5252
]} />);
5353
gid('cardslist').children().should('have.length', 1);
54+
cy.get('.loading').should('have.length', 1);
5455

5556
// Lets mock so that second request will return two results
5657
cy.intercept('**/cards*', createDataModel([
5758
{ id: '123', type: 'xd', hash: 'unique1' },
5859
{ id: '321', type: 'xd', hash: 'unique2' }
5960
], {}));
6061
gid('cardslist').children({ timeout: 6000 }).should('have.length', 2);
62+
cy.get('.success').should('have.length', 1);
6163
});
6264
});

src/components/MFCard/useTaskCards.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ import { DataModel } from '../../hooks/useResource';
44
import { Task } from '../../types';
55
import { Decorator } from '../DAG/DAGUtils';
66

7+
type CardResultState = 'loading' | 'timeout' | 'success';
8+
9+
export type CardResult = {
10+
status: CardResultState;
11+
cards: CardDefinition[];
12+
};
13+
714
export type CardDefinition = {
815
// ID of custom card
916
id?: string;
@@ -25,8 +32,13 @@ const POSTLOAD_POLL_INTERVAL = 5000;
2532

2633
//
2734
// Fetch list of card definitions for given task according to the decorators.
35+
// Returns a status and a list of cards (if any)
2836
//
29-
export default function useTaskCards(task: Task | null, decorators: Decorator[]): CardDefinition[] {
37+
export default function useTaskCards(task: Task | null, decorators: Decorator[]): CardResult {
38+
const [data, setData] = useState<CardDefinition[]>([]);
39+
const [poll, setPoll] = useState(false);
40+
const [status, setStatus] = useState<CardResultState>('loading');
41+
3042
const url = task ? taskCardsPath(task) : '';
3143
const expectedCards = decorators.filter((item) => item.name === 'card');
3244
// timeout in seconds
@@ -36,8 +48,6 @@ export default function useTaskCards(task: Task | null, decorators: Decorator[])
3648
}
3749
return timeout;
3850
}, 0);
39-
const [data, setData] = useState<CardDefinition[]>([]);
40-
const [poll, setPoll] = useState(false);
4151

4252
const aborter = useRef<AbortController>();
4353

@@ -77,9 +87,14 @@ export default function useTaskCards(task: Task | null, decorators: Decorator[])
7787
// Timeout timer is: task.finished_at + timeout from decorator attributes + 30seconds extra time.
7888
const timeout = taskFinishedAt ? taskFinishedAt + (maxTimeout + 30) * 1000 : false;
7989
// if we have enough cards (as presented by decorators list) or timeout has passed, skip request.
80-
if (expectedCards.length <= data.length || (timeout && timeout < Date.now())) {
90+
if (expectedCards.length <= data.length) {
91+
setPoll(false);
92+
setStatus('success');
93+
} else if (timeout && timeout < Date.now()) {
8194
setPoll(false);
95+
setStatus('timeout');
8296
} else {
97+
setStatus('loading');
8398
t = window.setTimeout(() => {
8499
fetchCards(url, true);
85100
}, POSTLOAD_POLL_INTERVAL);
@@ -89,7 +104,7 @@ export default function useTaskCards(task: Task | null, decorators: Decorator[])
89104
return () => {
90105
clearTimeout(t);
91106
};
92-
}, [url, poll, data, expectedCards, maxTimeout, taskFinishedAt]);
107+
}, [url, poll, data, expectedCards, maxTimeout, taskFinishedAt, status]);
93108

94109
// Initial fetch
95110
useEffect(() => {
@@ -100,5 +115,5 @@ export default function useTaskCards(task: Task | null, decorators: Decorator[])
100115
};
101116
}, [url]);
102117

103-
return data;
118+
return { status, cards: data };
104119
}

src/components/TitledSection/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from 'react';
22
import styled from 'styled-components';
33

44
type HeaderProps = {
5-
label?: string;
5+
label?: React.ReactNode | string;
66
actionbar?: React.ReactNode;
77
};
88

src/pages/Task/components/AnchorMenu.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { HEADER_SIZE_PX } from '../../../constants';
88

99
type AnchorItem = {
1010
key: string;
11-
label: string;
11+
label: React.ReactNode | string;
1212
position?: number;
1313
};
1414

src/pages/Task/components/AnchoredView.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import TaskSection from './TaskSection';
1010

1111
type AnchoredViewSection = {
1212
key: string;
13-
label: string;
13+
label: React.ReactNode | string;
1414
order: number;
1515
noTitle?: boolean;
1616
actionbar?: React.ReactNode;

src/pages/Task/components/TaskSection.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { TitledSectionHeader } from '../../../components/TitledSection';
88

99
type Props = {
1010
// Visible title for the section
11-
label: string;
11+
label: React.ReactNode | string;
1212
sectionkey: string;
1313
// Should title be hidden?
1414
noTitle?: boolean;

src/pages/Task/index.tsx

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ const Task: React.FC<TaskViewProps> = ({
191191
// Cards
192192
//
193193

194-
const cards = useTaskCards(
194+
const cardsResult = useTaskCards(
195195
task,
196196
task && dagResult.data ? dagResult.data.steps[task.step_name]?.decorators || [] : [],
197197
);
@@ -351,7 +351,7 @@ const Task: React.FC<TaskViewProps> = ({
351351
: []),
352352
// Render cards at the end of sections if enabled by feature flags.
353353
...(FEATURE_FLAGS.CARDS
354-
? cards.map((def) => ({
354+
? cardsResult.cards.map((def) => ({
355355
key: def.hash,
356356
order: 99,
357357
label: def.id ? `${t('card.card_id_title')}: ${def.id}` : `${t('card.card_title')}: ${def.type}`,
@@ -375,6 +375,37 @@ const Task: React.FC<TaskViewProps> = ({
375375
component: <CardIframe path={`${taskCardPath(task, def.hash)}?embed=true`} />,
376376
}))
377377
: []),
378+
// Show spinner if any cards are still loading
379+
...(FEATURE_FLAGS.CARDS
380+
? cardsResult.status === 'loading'
381+
? [
382+
{
383+
key: 'card_loading',
384+
order: 99,
385+
label: (
386+
<>
387+
<span>{t('card.card_loading')} </span>
388+
<Spinner sm />
389+
</>
390+
),
391+
component: <></>,
392+
},
393+
]
394+
: []
395+
: []),
396+
// Show error if cards were not fetched before timeout
397+
...(FEATURE_FLAGS.CARDS
398+
? cardsResult.status === 'timeout'
399+
? [
400+
{
401+
key: 'card_timeout',
402+
order: 99,
403+
label: t('card.card_timeout'),
404+
component: <></>,
405+
},
406+
]
407+
: []
408+
: []),
378409
].sort((a, b) => a.order - b.order)}
379410
/>
380411
</>

src/translations/en.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,9 @@ const en = {
325325
card: {
326326
card_id_title: 'Card ID',
327327
card_title: 'Card',
328-
download_card: 'Download Card HTML file',
328+
download_card: 'Download card HTML file',
329+
card_timeout: 'Timeout: loading cards',
330+
card_loading: 'Loading cards',
329331
},
330332
},
331333
};

0 commit comments

Comments
 (0)