Skip to content

Commit 7c11148

Browse files
ironAiken2claude
andcommitted
refactor(FR-2820): replace BAIProjectTable per-action callbacks with nameColumnActionProps
Consolidate onClickProjectEditButton / onClickDeactivateProject / onClickRestoreProject / onClickPurgeProject into a single nameColumnActionProps prop (object or (value, record, index) => props), mirroring the BAIModelDeploymentNodes pattern. The name column now only renders the title; ProjectPage composes the per-row action list (edit / deactivate / activate / purge) as a closure so all mutation and side-effect logic stays in the app layer instead of crossing the backend.ai-ui library boundary via individual callbacks. - BAIProjectTable: drop 4 callback props, add nameColumnActionProps and exported BAIProjectTableNameColumnActionProps type - ProjectPage: build the action list inline, reusing existing comp:BAIProjectTable.* i18n keys (no i18n changes) - stories: keep local QueryResolver callback API, adapt internally Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 301ac07 commit 7c11148

4 files changed

Lines changed: 177 additions & 125 deletions

File tree

packages/backend.ai-ui/src/components/fragments/BAIProjectTable.stories.tsx

Lines changed: 73 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@ import { BAIConfigProvider } from '../provider/BAIConfigProvider';
1010
import { BAIMetaDataProvider } from '../provider/BAIMetaDataProvider';
1111
import BAIProjectTable, {
1212
availableProjectSorterValues,
13+
type ProjectInList,
1314
} from './BAIProjectTable';
15+
import { DeleteFilled, SettingOutlined } from '@ant-design/icons';
1416
import type { Meta, StoryObj } from '@storybook/react-vite';
1517
import { App } from 'antd';
18+
import { BanIcon, UndoIcon } from 'lucide-react';
1619
import { useState } from 'react';
1720
import { graphql, useLazyLoadQuery } from 'react-relay';
1821

@@ -45,14 +48,10 @@ const meta: Meta<typeof BAIProjectTable> = {
4548
|------|------|---------|-------------|
4649
| \`projectFragment\` | \`BAIProjectTableFragment$key\` | - | GraphQL fragment reference (required) |
4750
| \`onChangeOrder\` | \`(order: ProjectSorterValue \\| null) => void\` | - | Callback when sort order changes |
48-
| \`onClickProjectEditButton\` | \`(project: Project) => void\` | - | Callback when edit button is clicked (required) |
49-
| \`onClickDeactivateProject\` | \`(project: Project) => Promise<void>\` | - | Async callback when deactivate is confirmed (required) |
50-
| \`onClickRestoreProject\` | \`(project: Project) => Promise<void>\` | - | Async callback when restore is confirmed (required) |
51-
| \`onClickPurgeProject\` | \`(project: Project) => void\` | - | Callback when purge is clicked (required) |
51+
| \`nameColumnActionProps\` | \`BAINameActionCellProps \\| ((value, record, index) => BAINameActionCellProps)\` | - | Props forwarded to the name column's \`BAINameActionCell\`. The page composes the per-row action list (edit / deactivate / activate / purge) here so action logic stays in the app layer |
5252
5353
## Pre-configured Columns
54-
- **Name**: Project name (sortable)
55-
- **Controls**: Edit, Deactivate, and Purge buttons
54+
- **Name**: Project name (sortable) with caller-composed row actions via \`nameColumnActionProps\`
5655
- **Domain**: Domain name (sortable)
5756
- **Description**: Project description
5857
- **Created At**: Creation timestamp (sortable)
@@ -88,32 +87,15 @@ For other props (loading, pagination, etc.), refer to [BAITable](?path=/docs/tab
8887
},
8988
},
9089
},
91-
onClickProjectEditButton: {
92-
action: 'edit-clicked',
93-
description: 'Callback when edit button is clicked',
94-
table: {
95-
type: { summary: '(project: Project) => void' },
96-
},
97-
},
98-
onClickDeactivateProject: {
99-
action: 'deactivate-confirmed',
100-
description: 'Async callback when deactivate is confirmed',
101-
table: {
102-
type: { summary: '(project: Project) => Promise<void>' },
103-
},
104-
},
105-
onClickRestoreProject: {
106-
action: 'restore-confirmed',
107-
description: 'Async callback when restore is confirmed',
108-
table: {
109-
type: { summary: '(project: Project) => Promise<void>' },
110-
},
111-
},
112-
onClickPurgeProject: {
113-
action: 'purge-clicked',
114-
description: 'Callback when purge is clicked',
90+
nameColumnActionProps: {
91+
control: false,
92+
description:
93+
"Props forwarded to the name column's BAINameActionCell. The page composes the per-row action list (edit / deactivate / activate / purge) here.",
11594
table: {
116-
type: { summary: '(project: Project) => void' },
95+
type: {
96+
summary:
97+
'BAINameActionCellProps | ((value, record, index) => BAINameActionCellProps)',
98+
},
11799
},
118100
},
119101
},
@@ -145,15 +127,14 @@ type Story = StoryObj<typeof BAIProjectTable>;
145127
interface QueryResolverProps extends Partial<
146128
Pick<
147129
React.ComponentProps<typeof BAIProjectTable>,
148-
| 'onClickProjectEditButton'
149-
| 'onClickDeactivateProject'
150-
| 'onClickRestoreProject'
151-
| 'onClickPurgeProject'
152-
| 'onChangeOrder'
153-
| 'loading'
154-
| 'order'
130+
'onChangeOrder' | 'loading' | 'order'
155131
>
156-
> {}
132+
> {
133+
onClickProjectEditButton?: (project: ProjectInList) => void;
134+
onClickDeactivateProject?: (project: ProjectInList) => Promise<void>;
135+
onClickRestoreProject?: (project: ProjectInList) => Promise<void>;
136+
onClickPurgeProject?: (project: ProjectInList) => void;
137+
}
157138

158139
const noop = () => {};
159140
const asyncNoop = async () => {};
@@ -194,10 +175,59 @@ const QueryResolver: React.FC<QueryResolverProps> = ({
194175
return (
195176
<BAIProjectTable
196177
projectFragment={projectNodes}
197-
onClickProjectEditButton={onClickProjectEditButton}
198-
onClickDeactivateProject={onClickDeactivateProject}
199-
onClickRestoreProject={onClickRestoreProject}
200-
onClickPurgeProject={onClickPurgeProject}
178+
nameColumnActionProps={(_value, record) => {
179+
const isModelStore = record.type === 'MODEL_STORE';
180+
return {
181+
actions: [
182+
{
183+
key: 'edit',
184+
title: 'Edit',
185+
icon: <SettingOutlined />,
186+
disabled: isModelStore,
187+
onClick: () => onClickProjectEditButton(record),
188+
},
189+
...(record.is_active
190+
? [
191+
{
192+
key: 'deactivate',
193+
title: 'Deactivate',
194+
icon: <BanIcon />,
195+
type: 'danger' as const,
196+
disabled: isModelStore,
197+
popConfirm: {
198+
title: 'Deactivate project',
199+
description: record.name,
200+
okButtonProps: { danger: true },
201+
okText: 'Deactivate',
202+
onConfirm: () => onClickDeactivateProject(record),
203+
},
204+
},
205+
]
206+
: [
207+
{
208+
key: 'activate',
209+
title: 'Activate',
210+
icon: <UndoIcon />,
211+
disabled: isModelStore,
212+
popConfirm: {
213+
title: 'Activate project',
214+
description: record.name,
215+
okText: 'Activate',
216+
onConfirm: () => onClickRestoreProject(record),
217+
},
218+
},
219+
{
220+
key: 'purge',
221+
title: 'Purge',
222+
icon: <DeleteFilled />,
223+
type: 'danger' as const,
224+
disabled: isModelStore,
225+
onClick: () => onClickPurgeProject(record),
226+
},
227+
]),
228+
],
229+
};
230+
}}
201231
onChangeOrder={onChangeOrder}
202232
loading={loading}
203233
order={order}

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

Lines changed: 31 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,17 @@ import {
55
import { toLocalId } from '../../helper';
66
import BAIResourceNumberWithIcon from '../BAIResourceNumberWithIcon';
77
import BAIText from '../BAIText';
8-
import { BAIColumnsType, BAITable, BAITableProps } from '../Table';
9-
import BAINameActionCell from '../Table/BAINameActionCell';
8+
import {
9+
BAIColumnsType,
10+
BAINameActionCell,
11+
BAINameActionCellProps,
12+
BAITable,
13+
BAITableProps,
14+
} from '../Table';
1015
import AllowedVfolderHostsWithPermission from './BAIAllowedVfolderHostsWithPermission';
11-
import { DeleteFilled, SettingOutlined } from '@ant-design/icons';
1216
import { Tag } from 'antd';
1317
import dayjs from 'dayjs';
1418
import * as _ from 'lodash-es';
15-
import { BanIcon, UndoIcon } from 'lucide-react';
1619
import { useTranslation } from 'react-i18next';
1720
import { graphql, useFragment } from 'react-relay';
1821

@@ -38,6 +41,14 @@ export type ProjectInList = NonNullable<
3841
NonNullable<BAIProjectTableFragment$data>[number]
3942
>;
4043

44+
export type BAIProjectTableNameColumnActionProps =
45+
| BAINameActionCellProps
46+
| ((
47+
value: any,
48+
record: ProjectInList,
49+
index: number,
50+
) => BAINameActionCellProps);
51+
4152
export interface BAIProjectTableProps extends Omit<
4253
BAITableProps<ProjectInList>,
4354
'dataSource' | 'columns' | 'rowKey' | 'onChangeOrder'
@@ -46,19 +57,21 @@ export interface BAIProjectTableProps extends Omit<
4657
onChangeOrder?: (
4758
order: (typeof availableProjectSorterValues)[number] | null,
4859
) => void;
49-
onClickProjectEditButton: (project: ProjectInList) => void;
50-
onClickDeactivateProject: (project: ProjectInList) => Promise<void>;
51-
onClickRestoreProject: (project: ProjectInList) => Promise<void>;
52-
onClickPurgeProject: (project: ProjectInList) => void;
60+
/**
61+
* Props forwarded to the `BAINameActionCell` used in the `name` column.
62+
* Accepts either a static object or a function receiving the column
63+
* render arguments `(value, record, index)` and returning props. The
64+
* page composes the per-row action list (edit / deactivate / activate /
65+
* purge) here so the action logic stays in the app layer rather than
66+
* being injected through individual callback props.
67+
*/
68+
nameColumnActionProps?: BAIProjectTableNameColumnActionProps;
5369
}
5470

5571
const BAIProjectTable = ({
5672
projectFragment,
5773
onChangeOrder,
58-
onClickProjectEditButton,
59-
onClickDeactivateProject,
60-
onClickRestoreProject,
61-
onClickPurgeProject,
74+
nameColumnActionProps,
6275
...tableProps
6376
}: BAIProjectTableProps) => {
6477
'use memo';
@@ -93,64 +106,16 @@ const BAIProjectTable = ({
93106
dataIndex: 'name',
94107
fixed: 'left',
95108
sorter: isEnableSorter('name'),
96-
render: (_value, record) => {
97-
const isModelStore = record.type === 'MODEL_STORE';
109+
render: (value, record, index) => {
110+
const resolvedProps =
111+
typeof nameColumnActionProps === 'function'
112+
? nameColumnActionProps(value, record, index)
113+
: nameColumnActionProps;
98114
return (
99115
<BAINameActionCell
100116
title={record.name}
101117
showActions="always"
102-
actions={[
103-
{
104-
key: 'edit',
105-
title: t('comp:BAIProjectTable.EditProject'),
106-
icon: <SettingOutlined />,
107-
disabled: isModelStore,
108-
onClick: () => {
109-
onClickProjectEditButton(record);
110-
},
111-
},
112-
...(record.is_active
113-
? [
114-
{
115-
key: 'deactivate',
116-
title: t('comp:BAIProjectTable.Deactivate'),
117-
icon: <BanIcon />,
118-
type: 'danger' as const,
119-
disabled: isModelStore,
120-
popConfirm: {
121-
title: t('comp:BAIProjectTable.DeactivateProject'),
122-
description: record.name,
123-
okButtonProps: { danger: true },
124-
okText: t('comp:BAIProjectTable.Deactivate'),
125-
onConfirm: () => onClickDeactivateProject(record),
126-
},
127-
},
128-
]
129-
: [
130-
{
131-
key: 'activate',
132-
title: t('comp:BAIProjectTable.Activate'),
133-
icon: <UndoIcon />,
134-
disabled: isModelStore,
135-
popConfirm: {
136-
title: t('comp:BAIProjectTable.ActivateProject'),
137-
description: record.name,
138-
okText: t('comp:BAIProjectTable.Activate'),
139-
onConfirm: () => onClickRestoreProject(record),
140-
},
141-
},
142-
{
143-
key: 'purge',
144-
title: t('comp:BAIProjectTable.Purge'),
145-
icon: <DeleteFilled />,
146-
type: 'danger' as const,
147-
disabled: isModelStore,
148-
onClick: () => {
149-
onClickPurgeProject(record);
150-
},
151-
},
152-
]),
153-
]}
118+
{...resolvedProps}
154119
/>
155120
);
156121
},

packages/backend.ai-ui/src/components/fragments/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,11 @@ export {
6262
availableProjectSorterKeys,
6363
availableProjectSorterValues,
6464
} from './BAIProjectTable';
65-
export type { BAIProjectTableProps, ProjectInList } from './BAIProjectTable';
65+
export type {
66+
BAIProjectTableProps,
67+
BAIProjectTableNameColumnActionProps,
68+
ProjectInList,
69+
} from './BAIProjectTable';
6670
export { default as BAIAllowedVfolderHostsWithPermission } from './BAIAllowedVfolderHostsWithPermission';
6771
export type { BAIAllowedVfolderHostsWithPermissionProps } from './BAIAllowedVfolderHostsWithPermission';
6872
export { default as BAIAdminContainerRegistrySelect } from './BAIAdminContainerRegistrySelect';

0 commit comments

Comments
 (0)