Skip to content

Commit b61a8af

Browse files
committed
fix(FR-2622): add delete-folder option and trash notification on model card deletion
1 parent 7005b4b commit b61a8af

2 files changed

Lines changed: 177 additions & 6 deletions

File tree

react/src/pages/AdminModelCardListPage.tsx

Lines changed: 172 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,32 @@
44
*/
55
import type { AdminModelCardListPageBulkDeleteMutation } from '../__generated__/AdminModelCardListPageBulkDeleteMutation.graphql';
66
import type { AdminModelCardListPageDeleteMutation } from '../__generated__/AdminModelCardListPageDeleteMutation.graphql';
7+
import type { AdminModelCardListPageDeleteVFolderMutation } from '../__generated__/AdminModelCardListPageDeleteVFolderMutation.graphql';
78
import type {
89
AdminModelCardListPageQuery,
910
AdminModelCardListPageQuery$data,
1011
ModelCardV2Filter,
1112
ModelCardV2OrderBy,
1213
} from '../__generated__/AdminModelCardListPageQuery.graphql';
1314
import AdminModelCardSettingModal from '../components/AdminModelCardSettingModal';
15+
import { useFolderExplorerOpener } from '../components/FolderExplorerOpener';
1416
import { convertToOrderBy, handleRowSelectionChange } from '../helper';
1517
import { useBAIPaginationOptionStateOnSearchParam } from '../hooks/reactPaginationQueryOptions';
18+
import { useSetBAINotification } from '../hooks/useBAINotification';
1619
import { useBAISettingUserState } from '../hooks/useBAISetting';
1720
import { useCurrentProjectValue } from '../hooks/useCurrentProject';
1821
import { SettingOutlined } from '@ant-design/icons';
19-
import { App, Typography } from 'antd';
22+
import { shapes } from '@dicebear/collection';
23+
import { createAvatar } from '@dicebear/core';
24+
import { App, Checkbox, Tooltip, Typography, theme } from 'antd';
2025
import {
2126
BAIButton,
2227
BAIColumnType,
2328
BAIDeleteConfirmModal,
2429
BAIFetchKeyButton,
2530
BAIFlex,
2631
BAIGraphQLPropertyFilter,
32+
BAILink,
2733
BAINameActionCell,
2834
BAISelectionLabel,
2935
BAITable,
@@ -62,7 +68,10 @@ const AdminModelCardListPage: React.FC = () => {
6268

6369
const { t } = useTranslation();
6470
const { message } = App.useApp();
71+
const { token } = theme.useToken();
6572
const { logger } = useBAILogger();
73+
const { upsertNotification } = useSetBAINotification();
74+
const { generateFolderPath } = useFolderExplorerOpener();
6675
const currentProject = useCurrentProjectValue();
6776
const [columnOverrides, setColumnOverrides] = useBAISettingUserState(
6877
'table_column_overrides.AdminModelCardListPage',
@@ -77,6 +86,7 @@ const AdminModelCardListPage: React.FC = () => {
7786
);
7887
const [deletingModelCard, setDeletingModelCard] =
7988
useState<ModelCardNode | null>(null);
89+
const [alsoDeleteFolder, setAlsoDeleteFolder] = useState(false);
8090
const [isBulkDeleteOpen, setIsBulkDeleteOpen] = useState(false);
8191
const {
8292
baiPaginationOption,
@@ -144,6 +154,13 @@ const AdminModelCardListPage: React.FC = () => {
144154
node {
145155
id
146156
name
157+
vfolderId
158+
vfolder {
159+
id
160+
metadata {
161+
name
162+
}
163+
}
147164
domainName
148165
projectId
149166
accessLevel
@@ -185,6 +202,15 @@ const AdminModelCardListPage: React.FC = () => {
185202
}
186203
`);
187204

205+
const [commitDeleteVFolder] =
206+
useMutation<AdminModelCardListPageDeleteVFolderMutation>(graphql`
207+
mutation AdminModelCardListPageDeleteVFolderMutation($vfolderId: UUID!) {
208+
deleteVfolderV2(vfolderId: $vfolderId) {
209+
id
210+
}
211+
}
212+
`);
213+
188214
const [commitBulkDeleteModelCards, isBulkDeleteInFlight] =
189215
useMutation<AdminModelCardListPageBulkDeleteMutation>(graphql`
190216
mutation AdminModelCardListPageBulkDeleteMutation(
@@ -428,6 +454,49 @@ const AdminModelCardListPage: React.FC = () => {
428454
description={t('adminModelCard.ConfirmDelete', {
429455
name: deletingModelCard?.name,
430456
})}
457+
requireConfirmInput
458+
extraContent={
459+
<Tooltip title={t('adminModelCard.AlsoDeleteModelFolderTooltip')}>
460+
<Checkbox
461+
checked={alsoDeleteFolder}
462+
onChange={(e) => setAlsoDeleteFolder(e.target.checked)}
463+
>
464+
{t('adminModelCard.AlsoDeleteModelFolder')}
465+
{deletingModelCard?.vfolder && (
466+
<span style={{ marginLeft: token.marginXXS }}>
467+
{'('}
468+
<img
469+
draggable={false}
470+
onDragStart={(e) => e.preventDefault()}
471+
style={{
472+
borderRadius: '0.25em',
473+
width: '1em',
474+
height: '1em',
475+
borderWidth: 0.5,
476+
borderStyle: 'solid',
477+
borderColor: token.colorBorder,
478+
userSelect: 'none',
479+
verticalAlign: 'middle',
480+
marginInline: token.marginXXS,
481+
}}
482+
src={createAvatar(shapes, {
483+
seed: deletingModelCard.vfolderId,
484+
shape3: [],
485+
}).toDataUri()}
486+
alt="VFolder Identicon"
487+
/>
488+
<BAILink
489+
to={generateFolderPath(deletingModelCard.vfolderId)}
490+
onClick={(e) => e.stopPropagation()}
491+
>
492+
{deletingModelCard.vfolder.metadata.name}
493+
</BAILink>
494+
{')'}
495+
</span>
496+
)}
497+
</Checkbox>
498+
</Tooltip>
499+
}
431500
onOk={() => {
432501
if (deletingModelCard) {
433502
return new Promise<void>((resolve, reject) => {
@@ -442,10 +511,104 @@ const AdminModelCardListPage: React.FC = () => {
442511
reject();
443512
return;
444513
}
445-
message.success(t('adminModelCard.ModelCardDeleted'));
446-
setDeletingModelCard(null);
447-
updateFetchKey();
448-
resolve();
514+
515+
const vfolderId = deletingModelCard.vfolderId;
516+
const folderName = deletingModelCard.vfolder?.metadata.name;
517+
const folderTrashSearch = new URLSearchParams({
518+
statusCategory: 'deleted',
519+
...(folderName
520+
? { filter: `name == "${folderName}"` }
521+
: {}),
522+
}).toString();
523+
524+
if (alsoDeleteFolder && vfolderId) {
525+
commitDeleteVFolder({
526+
variables: { vfolderId },
527+
onCompleted: (_vfolderData, vfolderErrors) => {
528+
if (vfolderErrors && vfolderErrors.length > 0) {
529+
logger.error(vfolderErrors[0]);
530+
message.error(
531+
vfolderErrors[0]?.message ||
532+
t('general.ErrorOccurred'),
533+
);
534+
upsertNotification({
535+
type: 'success',
536+
message: t(
537+
'adminModelCard.ModelCardDeletedFolderKept',
538+
),
539+
to: {
540+
pathname: '/data',
541+
search: 'statusCategory=deleted',
542+
},
543+
toText: t('adminModelCard.GoToTrash'),
544+
open: true,
545+
duration: 4,
546+
extraData: null,
547+
});
548+
} else {
549+
upsertNotification({
550+
type: 'success',
551+
message: t(
552+
'adminModelCard.ModelCardAndFolderDeleted',
553+
),
554+
to: {
555+
pathname: '/data',
556+
search: folderTrashSearch,
557+
},
558+
toText: t('adminModelCard.GoToTrash'),
559+
open: true,
560+
duration: 4,
561+
extraData: null,
562+
});
563+
}
564+
setDeletingModelCard(null);
565+
setAlsoDeleteFolder(false);
566+
updateFetchKey();
567+
resolve();
568+
},
569+
onError: (error) => {
570+
logger.error(error);
571+
message.error(
572+
error?.message || t('general.ErrorOccurred'),
573+
);
574+
upsertNotification({
575+
type: 'success',
576+
message: t(
577+
'adminModelCard.ModelCardDeletedFolderKept',
578+
),
579+
to: {
580+
pathname: '/data',
581+
search: 'statusCategory=deleted',
582+
},
583+
toText: t('adminModelCard.GoToTrash'),
584+
open: true,
585+
duration: 4,
586+
extraData: null,
587+
});
588+
setDeletingModelCard(null);
589+
setAlsoDeleteFolder(false);
590+
updateFetchKey();
591+
resolve();
592+
},
593+
});
594+
} else {
595+
upsertNotification({
596+
type: 'success',
597+
message: t('adminModelCard.ModelCardDeletedFolderKept'),
598+
to: {
599+
pathname: '/data',
600+
search: 'statusCategory=deleted',
601+
},
602+
toText: t('adminModelCard.GoToTrash'),
603+
open: true,
604+
duration: 4,
605+
extraData: null,
606+
});
607+
setDeletingModelCard(null);
608+
setAlsoDeleteFolder(false);
609+
updateFetchKey();
610+
resolve();
611+
}
449612
},
450613
onError: (error) => {
451614
logger.error(error);
@@ -456,7 +619,10 @@ const AdminModelCardListPage: React.FC = () => {
456619
});
457620
}
458621
}}
459-
onCancel={() => setDeletingModelCard(null)}
622+
onCancel={() => {
623+
setDeletingModelCard(null);
624+
setAlsoDeleteFolder(false);
625+
}}
460626
/>
461627
<BAIDeleteConfirmModal
462628
open={isBulkDeleteOpen}

resources/i18n/en.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
"AccessLevelUpdated": "Access level has been updated.",
1212
"AddFramework": "Add framework",
1313
"AddLabel": "Add label",
14+
"AlsoDeleteModelFolder": "Also delete the associated model folder",
15+
"AlsoDeleteModelFolderTooltip": "The model folder will be moved to trash. You can permanently delete it from Data > Trash.",
1416
"Architecture": "Architecture",
1517
"ArchitectureTooltip": "The model architecture (e.g., Transformer, CNN, RNN).",
1618
"Author": "Author",
@@ -32,12 +34,15 @@
3234
"EnterProjectId": "Enter project ID",
3335
"Framework": "Framework",
3436
"FrameworkTooltip": "Frameworks used by the model (e.g., PyTorch, TensorFlow). Press Enter to add.",
37+
"GoToTrash": "Go to Data > Trash",
3538
"Label": "Label",
3639
"LabelTooltip": "Custom tags for categorizing and filtering models. Press Enter to add.",
3740
"License": "License",
3841
"LicenseTooltip": "The license under which the model is distributed (e.g., MIT, Apache-2.0).",
42+
"ModelCardAndFolderDeleted": "Model card and folder have been moved to trash.",
3943
"ModelCardCreated": "Model card has been created.",
4044
"ModelCardDeleted": "Model card has been deleted.",
45+
"ModelCardDeletedFolderKept": "Model card has been deleted. The model folder was not deleted.",
4146
"ModelCardUpdated": "Model card has been updated.",
4247
"ModelCards": "Model Cards",
4348
"ModelStorageFolder": "Model Storage Folder",

0 commit comments

Comments
 (0)