Skip to content

Commit ee60edb

Browse files
committed
feat(FR-1499): implement delete and restore artifacts
1 parent 4dfe10a commit ee60edb

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1023
-101
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { BAIActivateArtifactsModalArtifactsFragment$key } from '../../__generated__/BAIActivateArtifactsModalArtifactsFragment.graphql';
2+
import { BAIActivateArtifactsModalArtifactsFragmentRestoreArtifactsMutation } from '../../__generated__/BAIActivateArtifactsModalArtifactsFragmentRestoreArtifactsMutation.graphql';
3+
import { toLocalId } from '../../helper';
4+
import BAIUnmountAfterClose from '../BAIUnmountAfterClose';
5+
import { App, Modal, ModalProps, Typography } from 'antd';
6+
import { useTranslation } from 'react-i18next';
7+
import { graphql, useFragment, useMutation } from 'react-relay';
8+
9+
export type BAIActivateArtifactsModalArtifactsFragmentKey =
10+
BAIActivateArtifactsModalArtifactsFragment$key;
11+
12+
export interface BAIActivateArtifactsModalProps extends ModalProps {
13+
selectedArtifactsFragment: BAIActivateArtifactsModalArtifactsFragmentKey;
14+
}
15+
16+
const BAIActivateArtifactsModal = ({
17+
selectedArtifactsFragment,
18+
onOk,
19+
onCancel,
20+
...props
21+
}: BAIActivateArtifactsModalProps) => {
22+
const { t } = useTranslation();
23+
const { message } = App.useApp();
24+
25+
const selectedArtifacts =
26+
useFragment<BAIActivateArtifactsModalArtifactsFragment$key>(
27+
graphql`
28+
fragment BAIActivateArtifactsModalArtifactsFragment on Artifact
29+
@relay(plural: true) {
30+
id
31+
name
32+
}
33+
`,
34+
selectedArtifactsFragment,
35+
);
36+
37+
const [restoreArtifacts, isInflightRestoreArtifacts] =
38+
useMutation<BAIActivateArtifactsModalArtifactsFragmentRestoreArtifactsMutation>(
39+
graphql`
40+
mutation BAIActivateArtifactsModalArtifactsFragmentRestoreArtifactsMutation(
41+
$input: RestoreArtifactsInput!
42+
) {
43+
restoreArtifacts(input: $input) {
44+
artifacts {
45+
id
46+
availability
47+
}
48+
}
49+
}
50+
`,
51+
);
52+
53+
return (
54+
<BAIUnmountAfterClose>
55+
<Modal
56+
title={t('comp:BAIActivateArtifactsModal.ActivateArtifacts')}
57+
centered
58+
{...props}
59+
onOk={(e) => {
60+
restoreArtifacts({
61+
variables: {
62+
input: {
63+
artifactIds: selectedArtifacts.map((a) => toLocalId(a.id)),
64+
},
65+
},
66+
onCompleted: (res, errors) => {
67+
if (errors && errors.length > 0) {
68+
errors.forEach((err) =>
69+
message.error(
70+
err.message ??
71+
t(
72+
'comp:BAIActivateArtifactsModal.FailedToActivateArtifacts',
73+
),
74+
),
75+
);
76+
return;
77+
}
78+
message.success(
79+
t('comp:BAIActivateArtifactsModal.SuccessfullyActivated'),
80+
);
81+
onOk?.(e);
82+
},
83+
onError: (err) => {
84+
message.error(
85+
err.message ??
86+
t('comp:BAIActivateArtifactsModal.FailedToActivateArtifacts'),
87+
);
88+
},
89+
});
90+
}}
91+
onCancel={(e) => {
92+
onCancel?.(e);
93+
}}
94+
okText={t('comp:BAIActivateArtifactsModal.Activate')}
95+
okButtonProps={{ loading: isInflightRestoreArtifacts }}
96+
>
97+
<Typography.Text>
98+
{selectedArtifacts.length === 1
99+
? t(
100+
'comp:BAIActivateArtifactsModal.AreYouSureYouWantToActivateOne',
101+
{ name: selectedArtifacts[0].name },
102+
)
103+
: t(
104+
'comp:BAIActivateArtifactsModal.AreYouSureYouWantToActivateSome',
105+
{ count: selectedArtifacts.length },
106+
)}
107+
</Typography.Text>
108+
</Modal>
109+
</BAIUnmountAfterClose>
110+
);
111+
};
112+
113+
export default BAIActivateArtifactsModal;

packages/backend.ai-ui/src/components/fragments/BAIArtifactTable.tsx

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ import BAIArtifactRevisionDownloadButton from './BAIArtifactRevisionDownloadButt
1616
import BAIArtifactStatusTag from './BAIArtifactStatusTag';
1717
import BAIArtifactTypeTag from './BAIArtifactTypeTag';
1818
import { SyncOutlined } from '@ant-design/icons';
19-
import { TableColumnsType, theme, Typography } from 'antd';
19+
import { Button, TableColumnsType, theme, Typography } from 'antd';
2020
import dayjs from 'dayjs';
2121
import relativeTime from 'dayjs/plugin/relativeTime';
2222
import _ from 'lodash';
23-
import { Package, Container, Brain } from 'lucide-react';
23+
import { Package, Container, Brain, BanIcon, UndoIcon } from 'lucide-react';
2424
import { useTranslation } from 'react-i18next';
2525
import { graphql, useFragment } from 'react-relay';
2626

@@ -77,11 +77,15 @@ export interface BAIArtifactTableProps
7777
extends Omit<BAITableProps<Artifact>, 'dataSource' | 'columns' | 'rowKey'> {
7878
artifactFragment: BAIArtifactTableArtifactFragment$key;
7979
onClickPull: (artifactId: string, revisionId: string) => void;
80+
onClickDelete: (artifactId: string) => void;
81+
onClickRestore: (artifactId: string) => void;
8082
}
8183

8284
const BAIArtifactTable = ({
8385
artifactFragment,
8486
onClickPull,
87+
onClickDelete,
88+
onClickRestore,
8589
...tableProps
8690
}: BAIArtifactTableProps) => {
8791
const { token } = theme.useToken();
@@ -97,6 +101,7 @@ const BAIArtifactTable = ({
97101
description
98102
updatedAt
99103
scannedAt
104+
availability
100105
...BAIArtifactTypeTagFragment
101106
latestVersion: revisions(
102107
first: 1
@@ -125,9 +130,9 @@ const BAIArtifactTable = ({
125130
key: 'name',
126131
render: (name: string, record: Artifact) => {
127132
return (
128-
<BAIFlex direction="column" align="start">
133+
<BAIFlex direction="column" align="start" wrap="wrap">
129134
<BAIFlex gap={'xs'}>
130-
<BAILink to={'/reservoir/' + toLocalId(record.id)}>
135+
<BAILink to={'/reservoir/' + toLocalId(record.id)} style={{}}>
131136
{name}
132137
</BAILink>
133138
<BAIArtifactTypeTag artifactTypeFrgmt={record} />
@@ -143,7 +148,42 @@ const BAIArtifactTable = ({
143148
</BAIFlex>
144149
);
145150
},
146-
width: '30%',
151+
},
152+
{
153+
title: t('comp:BAIArtifactTable.Controls'),
154+
key: 'controls',
155+
render: (record: Artifact) => {
156+
const availability = record.availability;
157+
if (availability === 'ALIVE') {
158+
return (
159+
<Button
160+
title={t('comp:BAIArtifactTable.Deactivate')}
161+
size="small"
162+
type="text"
163+
style={{
164+
color: token.colorError,
165+
background: token.colorErrorBg,
166+
}}
167+
icon={<BanIcon />}
168+
onClick={() => onClickDelete(record.id)}
169+
/>
170+
);
171+
} else if (availability === 'DELETED') {
172+
return (
173+
<Button
174+
title={t('comp:BAIArtifactTable.Activate')}
175+
size="small"
176+
type="text"
177+
icon={<UndoIcon />}
178+
style={{
179+
color: token.colorInfo,
180+
background: token.colorInfoBg,
181+
}}
182+
onClick={() => onClickRestore(record.id)}
183+
/>
184+
);
185+
}
186+
},
147187
},
148188
{
149189
title: t('comp:BAIArtifactRevisionTable.LatestVersion'),
@@ -171,7 +211,6 @@ const BAIArtifactTable = ({
171211
</BAIFlex>
172212
);
173213
},
174-
width: '25%',
175214
},
176215
{
177216
title: t('comp:BAIArtifactRevisionTable.Size'),
@@ -187,7 +226,6 @@ const BAIArtifactTable = ({
187226
</BAIText>
188227
);
189228
},
190-
width: '15%',
191229
},
192230
{
193231
title: t('comp:BAIArtifactTable.Scanned'),
@@ -203,7 +241,6 @@ const BAIArtifactTable = ({
203241
</Typography.Text>
204242
);
205243
},
206-
width: '15%',
207244
},
208245
{
209246
title: t('comp:BAIArtifactRevisionTable.Updated'),
@@ -219,7 +256,6 @@ const BAIArtifactTable = ({
219256
</Typography.Text>
220257
);
221258
},
222-
width: '15%',
223259
},
224260
];
225261

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { BAIDeactivateArtifactsModalArtifactsFragment$key } from '../../__generated__/BAIDeactivateArtifactsModalArtifactsFragment.graphql';
2+
import { BAIDeactivateArtifactsModalDeleteArtifactsMutation } from '../../__generated__/BAIDeactivateArtifactsModalDeleteArtifactsMutation.graphql';
3+
import { toLocalId } from '../../helper';
4+
import BAIUnmountAfterClose from '../BAIUnmountAfterClose';
5+
import { App, Modal, ModalProps, Typography } from 'antd';
6+
import { useTranslation } from 'react-i18next';
7+
import { graphql, useFragment, useMutation } from 'react-relay';
8+
9+
export type BAIDeactivateArtifactsModalArtifactsFragmentKey =
10+
BAIDeactivateArtifactsModalArtifactsFragment$key;
11+
12+
export interface BAIDeactivateArtifactsModalProps extends ModalProps {
13+
selectedArtifactsFragment: BAIDeactivateArtifactsModalArtifactsFragmentKey;
14+
}
15+
16+
const BAIDeactivateArtifactsModal = ({
17+
selectedArtifactsFragment,
18+
onOk,
19+
onCancel,
20+
...modalProps
21+
}: BAIDeactivateArtifactsModalProps) => {
22+
const { t } = useTranslation();
23+
const { message } = App.useApp();
24+
const selectedArtifacts =
25+
useFragment<BAIDeactivateArtifactsModalArtifactsFragment$key>(
26+
graphql`
27+
fragment BAIDeactivateArtifactsModalArtifactsFragment on Artifact
28+
@relay(plural: true) {
29+
id
30+
name
31+
}
32+
`,
33+
selectedArtifactsFragment,
34+
);
35+
36+
const [deleteArtifacts, isInflightDeleteArtifacts] =
37+
useMutation<BAIDeactivateArtifactsModalDeleteArtifactsMutation>(graphql`
38+
mutation BAIDeactivateArtifactsModalDeleteArtifactsMutation(
39+
$input: DeleteArtifactsInput!
40+
) {
41+
deleteArtifacts(input: $input) {
42+
artifacts {
43+
id
44+
availability
45+
}
46+
}
47+
}
48+
`);
49+
50+
return (
51+
<BAIUnmountAfterClose>
52+
<Modal
53+
title={t('comp:BAIDeactivateArtifactsModal.DeactivateArtifacts')}
54+
centered
55+
okText={t('comp:BAIDeactivateArtifactsModal.Deactivate')}
56+
onOk={(e) => {
57+
deleteArtifacts({
58+
variables: {
59+
input: {
60+
artifactIds: selectedArtifacts.map((a) => toLocalId(a.id)),
61+
},
62+
},
63+
onCompleted: (res, errors) => {
64+
if (errors && errors.length > 0) {
65+
errors.forEach((err) =>
66+
message.error(
67+
err.message ??
68+
t(
69+
'comp:BAIDeactivateArtifactsModal.FailedToDeactivateArtifacts',
70+
),
71+
),
72+
);
73+
return;
74+
}
75+
message.success(
76+
t('comp:BAIDeactivateArtifactsModal.SuccessfullyDeactivated'),
77+
);
78+
onOk?.(e);
79+
},
80+
onError: (err) => {
81+
message.error(
82+
err.message ??
83+
t(
84+
'comp:BAIDeactivateArtifactsModal.FailedToDeactivateArtifacts',
85+
),
86+
);
87+
},
88+
});
89+
}}
90+
onCancel={(e) => {
91+
onCancel?.(e);
92+
}}
93+
okButtonProps={{ danger: true, loading: isInflightDeleteArtifacts }}
94+
{...modalProps}
95+
>
96+
<Typography.Text>
97+
{selectedArtifacts.length === 1
98+
? t(
99+
'comp:BAIDeactivateArtifactsModal.AreYouSureYouWantToDeactivateOne',
100+
{
101+
name: selectedArtifacts[0].name,
102+
},
103+
)
104+
: t(
105+
'comp:BAIDeactivateArtifactsModal.AreYouSureYouWantToDeactivateSome',
106+
{
107+
count: selectedArtifacts.length,
108+
},
109+
)}
110+
</Typography.Text>
111+
</Modal>
112+
</BAIUnmountAfterClose>
113+
);
114+
};
115+
116+
export default BAIDeactivateArtifactsModal;

0 commit comments

Comments
 (0)