Skip to content

Commit cf4bbab

Browse files
committed
feat(FR-1494): refactor session notification with dedicated component for improved UX (#4359)
Resolves #4306 ([FR-1494](https://lablup.atlassian.net/browse/FR-1494)) ## Summary Refactored session notification component to improve UX for session creation and app launch. Created a dedicated `BAIComputeSessionNodeNotificationItem` component to handle compute session notifications with better state management and auto-refresh capabilities. ## Changes - Created new `BAIComputeSessionNodeNotificationItem.tsx` component with: - Auto-refresh interval based on session status - Automatic cleanup when session terminates - Improved status tracking and UI feedback - Refactored `BAINodeNotificationItem.tsx` to use the new dedicated component - Fixed ESLint warnings and improved code organization ## Why Per FR-1494, this improves the user experience for session creation and app launch by providing better real-time feedback and status updates through a dedicated notification component. [FR-1494]: https://lablup.atlassian.net/browse/FR-1494?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
1 parent c5a90ea commit cf4bbab

File tree

2 files changed

+168
-71
lines changed

2 files changed

+168
-71
lines changed
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import SessionActionButtons from './ComputeSessionNodeItems/SessionActionButtons';
2+
import SessionStatusTag from './ComputeSessionNodeItems/SessionStatusTag';
3+
import { useUpdateEffect } from 'ahooks';
4+
import { BAIFlex, BAILink, BAINotificationItem } from 'backend.ai-ui';
5+
import dayjs from 'dayjs';
6+
import React, { useEffect, useState } from 'react';
7+
import { useTranslation } from 'react-i18next';
8+
import {
9+
fetchQuery,
10+
graphql,
11+
useFragment,
12+
useRelayEnvironment,
13+
} from 'react-relay';
14+
import { BAIComputeSessionNodeNotificationItemFragment$key } from 'src/__generated__/BAIComputeSessionNodeNotificationItemFragment.graphql';
15+
import { BAIComputeSessionNodeNotificationItemRefreshQuery } from 'src/__generated__/BAIComputeSessionNodeNotificationItemRefreshQuery.graphql';
16+
import {
17+
NotificationState,
18+
useSetBAINotification,
19+
} from 'src/hooks/useBAINotification';
20+
import { useInterval } from 'src/hooks/useIntervalValue';
21+
22+
interface BAINodeNotificationItemProps {
23+
notification: NotificationState;
24+
sessionFrgmt: BAIComputeSessionNodeNotificationItemFragment$key | null;
25+
showDate?: boolean;
26+
}
27+
const BAIComputeSessionNodeNotificationItem: React.FC<
28+
BAINodeNotificationItemProps
29+
> = ({ sessionFrgmt, showDate, notification }) => {
30+
const { destroyNotification } = useSetBAINotification();
31+
const { t } = useTranslation();
32+
const node = useFragment(
33+
graphql`
34+
fragment BAIComputeSessionNodeNotificationItemFragment on ComputeSessionNode {
35+
row_id
36+
id
37+
name
38+
status
39+
...SessionActionButtonsFragment
40+
...SessionStatusTagFragment
41+
}
42+
`,
43+
sessionFrgmt,
44+
);
45+
46+
// TODO: delete this when Status subscription is implemented
47+
const [delay, setDelay] = useState<number | null>(null);
48+
UNSAFE_useAutoRefreshInterval(node?.id || '', delay);
49+
useEffect(() => {
50+
if (
51+
!node?.status ||
52+
node?.status === 'TERMINATED' ||
53+
node?.status === 'CANCELLED'
54+
) {
55+
setDelay(null);
56+
} else if (node?.status === 'RUNNING') {
57+
setDelay(15000);
58+
} else {
59+
setDelay(3000);
60+
}
61+
}, [node?.status]);
62+
// ---
63+
64+
useUpdateEffect(() => {
65+
if (node?.status === 'TERMINATED' || node?.status === 'CANCELLED') {
66+
setTimeout(() => {
67+
destroyNotification(notification.key);
68+
}, 3000);
69+
}
70+
}, [node?.status]);
71+
72+
return (
73+
node && (
74+
<BAINotificationItem
75+
title={
76+
<span>
77+
{t('general.Session')}:&nbsp;
78+
<BAILink
79+
style={{
80+
fontWeight: 'normal',
81+
}}
82+
to={{
83+
pathname: '/session',
84+
search: node.row_id
85+
? new URLSearchParams({
86+
sessionDetail: node.row_id,
87+
}).toString()
88+
: undefined,
89+
}}
90+
onClick={() => {
91+
destroyNotification(notification.key);
92+
}}
93+
>{`${node.name}`}</BAILink>
94+
</span>
95+
}
96+
description={
97+
<BAIFlex justify="between">
98+
<SessionStatusTag
99+
sessionFrgmt={node || null}
100+
showQueuePosition={false}
101+
showTooltip={false}
102+
/>
103+
<SessionActionButtons
104+
compact
105+
size="small"
106+
sessionFrgmt={node || null}
107+
hiddenButtonKeys={['containerCommit']}
108+
/>
109+
</BAIFlex>
110+
}
111+
footer={
112+
showDate ? dayjs(notification.created).format('lll') : undefined
113+
}
114+
/>
115+
)
116+
);
117+
};
118+
119+
export default BAIComputeSessionNodeNotificationItem;
120+
121+
const UNSAFE_useAutoRefreshInterval = (
122+
sessionId: string,
123+
delay: number | null,
124+
) => {
125+
// const [delay, setDelay] = useState<number|null>(3000);
126+
const relayEnv = useRelayEnvironment();
127+
128+
useInterval(() => {
129+
fetchQuery<BAIComputeSessionNodeNotificationItemRefreshQuery>(
130+
relayEnv,
131+
graphql`
132+
query BAIComputeSessionNodeNotificationItemRefreshQuery(
133+
$id: GlobalIDField!
134+
) {
135+
compute_session_node(id: $id) {
136+
...BAINodeNotificationItemFragment
137+
}
138+
}
139+
`,
140+
{ id: sessionId },
141+
).toPromise();
142+
}, delay);
143+
};

react/src/components/BAINodeNotificationItem.tsx

Lines changed: 25 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,40 @@
1-
import {
2-
NotificationState,
3-
useSetBAINotification,
4-
} from '../hooks/useBAINotification';
5-
import SessionActionButtons from './ComputeSessionNodeItems/SessionActionButtons';
6-
import SessionStatusTag from './ComputeSessionNodeItems/SessionStatusTag';
7-
import { BAIFlex, BAILink, BAINotificationItem } from 'backend.ai-ui';
8-
import dayjs from 'dayjs';
1+
import { NotificationState } from '../hooks/useBAINotification';
2+
import BAIComputeSessionNodeNotificationItem from './BAIComputeSessionNodeNotificationItem';
93
import React from 'react';
10-
import { useTranslation } from 'react-i18next';
114
import { graphql, useRefetchableFragment } from 'react-relay';
125
import { BAINodeNotificationItemFragment$key } from 'src/__generated__/BAINodeNotificationItemFragment.graphql';
136

7+
const nodeFragmentOperation = graphql`
8+
fragment BAINodeNotificationItemFragment on Node
9+
@refetchable(queryName: "BAINodeNotificationItemRefetchQuery") {
10+
... on ComputeSessionNode {
11+
__typename
12+
status
13+
name
14+
row_id
15+
...BAIComputeSessionNodeNotificationItemFragment
16+
@alias(as: "sessionFrgmt")
17+
}
18+
... on VirtualFolderNode {
19+
__typename
20+
status
21+
}
22+
}
23+
`;
24+
1425
const BAINodeNotificationItem: React.FC<{
1526
notification: NotificationState;
1627
nodeFrgmt: BAINodeNotificationItemFragment$key | null;
1728
showDate?: boolean;
1829
}> = ({ notification, nodeFrgmt, showDate }) => {
19-
const { destroyNotification } = useSetBAINotification();
20-
const { t } = useTranslation();
21-
const [node] = useRefetchableFragment(
22-
graphql`
23-
fragment BAINodeNotificationItemFragment on Node
24-
@refetchable(queryName: "BAINodeNotificationItemRefetchQuery") {
25-
... on ComputeSessionNode {
26-
__typename
27-
status
28-
name
29-
row_id
30-
...SessionActionButtonsFragment @alias(as: "sessionFrgmt")
31-
...SessionStatusTagFragment @alias(as: "sessionTagFrgmt")
32-
}
33-
... on VirtualFolderNode {
34-
__typename
35-
status
36-
}
37-
}
38-
`,
39-
nodeFrgmt,
40-
);
30+
const [node] = useRefetchableFragment(nodeFragmentOperation, nodeFrgmt);
4131

4232
if (node?.__typename === 'ComputeSessionNode') {
4333
return (
44-
<BAINotificationItem
45-
title={
46-
<span>
47-
{t('general.Session')}:&nbsp;
48-
<BAILink
49-
style={{
50-
fontWeight: 'normal',
51-
}}
52-
to={{
53-
pathname: '/session',
54-
search: node.row_id
55-
? new URLSearchParams({
56-
sessionDetail: node.row_id,
57-
}).toString()
58-
: undefined,
59-
}}
60-
onClick={() => {
61-
destroyNotification(notification.key);
62-
}}
63-
>{`${node.name}`}</BAILink>
64-
</span>
65-
}
66-
description={
67-
<BAIFlex justify="between">
68-
<SessionStatusTag
69-
sessionFrgmt={node.sessionTagFrgmt || null}
70-
showQueuePosition={false}
71-
showTooltip={false}
72-
/>
73-
<SessionActionButtons
74-
compact
75-
size="small"
76-
sessionFrgmt={node.sessionFrgmt || null}
77-
hiddenButtonKeys={['containerCommit']}
78-
/>
79-
</BAIFlex>
80-
}
81-
footer={
82-
showDate ? dayjs(notification.created).format('lll') : undefined
83-
}
34+
<BAIComputeSessionNodeNotificationItem
35+
notification={notification}
36+
sessionFrgmt={node.sessionFrgmt || null}
37+
showDate={showDate}
8438
/>
8539
);
8640
} else if (node?.__typename === 'VirtualFolderNode') {

0 commit comments

Comments
 (0)