Skip to content

Commit

Permalink
feat: introduce exporting CSV module (#2513)
Browse files Browse the repository at this point in the history
### TL;DR

This PR adds CSV export functionality to the Keypair, Project, and User Resource Policy Lists, along with associated UI updates.

### What changed?
- Added `exportAsCSV` and `exportCSVWithFormattingRules` functions to helper.
- Integrated CSV export options into the UI of Keypair Resource Policy list, Project Resource Policy list, and User Resource Policy List.
- Updated translations to support new CSV export functionalities.

### How to test?
1. Navigate to each Resource Policy List in the UI.
2. Use the new 'Tools' dropdown and select 'Export CSV'.
3. Verify the CSV file content and format.

### Why make this change?
To provide users with an easy way to export and share resource policy data.

**Checklist:** (if applicable)

- [ ] Mention to the original issue
- [ ] Documentation
- [ ] Minium required manager version
- [ ] Specific setting for review (eg., KB link, endpoint or how to setup)
- [ ] Minimum requirements to check during review
- [ ] Test case(s) to demonstrate the difference of before/after
  • Loading branch information
ironAiken2 committed Aug 6, 2024
1 parent e19ce31 commit ab202d9
Show file tree
Hide file tree
Showing 24 changed files with 345 additions and 34 deletions.
77 changes: 72 additions & 5 deletions react/src/components/KeypairResourcePolicyList.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { localeCompare, numberSorterWithInfinityValue } from '../helper';
import {
exportCSVWithFormattingRules,
localeCompare,
numberSorterWithInfinityValue,
} from '../helper';
import { useSuspendedBackendaiClient, useUpdatableState } from '../hooks';
import Flex from './Flex';
import KeypairResourcePolicySettingModal, {
Expand All @@ -15,12 +19,22 @@ import {
import { KeypairResourcePolicySettingModalFragment$key } from './__generated__/KeypairResourcePolicySettingModalFragment.graphql';
import {
DeleteOutlined,
DownOutlined,
PlusOutlined,
ReloadOutlined,
SettingOutlined,
} from '@ant-design/icons';
import { useLocalStorageState } from 'ahooks';
import { App, Button, Popconfirm, Table, Tag, theme, Typography } from 'antd';
import {
App,
Button,
Dropdown,
Popconfirm,
Space,
Table,
Tag,
theme,
} from 'antd';
import { AnyObject } from 'antd/es/_util/type';
import { ColumnsType, ColumnType } from 'antd/es/table';
import graphql from 'babel-plugin-relay/macro';
Expand Down Expand Up @@ -272,6 +286,38 @@ const KeypairResourcePolicyList: React.FC<KeypairResourcePolicyListProps> = (
},
);

const handleExportCSV = () => {
if (!keypair_resource_policies) {
message.error(t('resourcePolicy.NoDataToExport'));
return;
}

const columnkeys = _.without(displayedColumnKeys, 'control');
const responseData = _.map(keypair_resource_policies, (policy) => {
return _.pick(
policy,
columnkeys.map((key) => key as keyof KeypairResourcePolicies),
);
});

exportCSVWithFormattingRules(
responseData as KeypairResourcePolicies[],
{
total_resource_slots: (text) =>
_.isEmpty(text) ? '-' : JSON.stringify(text),
max_concurrent_sessions: (text) =>
text === UNLIMITED_MAX_CONCURRENT_SESSIONS ? '-' : text,
max_containers_per_session: (text) =>
text === UNLIMITED_MAX_CONTAINERS_PER_SESSIONS ? '-' : text,
idle_timeout: (text) => (text ? text : '-'),
max_session_lifetime: (text) => (text ? text : '-'),
allowed_vfolder_hosts: (text) =>
_.isEmpty(text) ? '-' : _.keys(JSON.parse(text)).join(', '),
},
'keypair_resource_policies',
);
};

return (
<Flex direction="column" align="stretch">
<Flex
Expand All @@ -286,9 +332,30 @@ const KeypairResourcePolicyList: React.FC<KeypairResourcePolicyListProps> = (
}}
>
<Flex direction="column" align="start">
<Typography.Text style={{ margin: 0, padding: 0 }}>
{t('resourcePolicy.KeypairResourcePolicy')}
</Typography.Text>
<Dropdown
menu={{
items: [
{
key: 'exportCSV',
label: t('resourcePolicy.ExportCSV'),
onClick: () => {
handleExportCSV();
},
},
],
}}
>
<Button
type="link"
style={{ padding: 0 }}
onClick={(e) => e.preventDefault()}
>
<Space style={{ color: token.colorLinkHover }}>
{t('resourcePolicy.Tools')}
<DownOutlined />
</Space>
</Button>
</Dropdown>
</Flex>
<Flex direction="row" gap={'xs'} wrap="wrap" style={{ flexShrink: 1 }}>
<Flex gap={'xs'}>
Expand Down
68 changes: 64 additions & 4 deletions react/src/components/ProjectResourcePolicyList.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
bytesToGB,
exportCSVWithFormattingRules,
filterEmptyItem,
localeCompare,
numberSorterWithInfinityValue,
Expand All @@ -16,12 +17,21 @@ import {
import { ProjectResourcePolicySettingModalFragment$key } from './__generated__/ProjectResourcePolicySettingModalFragment.graphql';
import {
DeleteOutlined,
DownOutlined,
PlusOutlined,
ReloadOutlined,
SettingOutlined,
} from '@ant-design/icons';
import { useLocalStorageState } from 'ahooks';
import { Button, message, Popconfirm, Table, theme, Typography } from 'antd';
import {
Button,
Dropdown,
message,
Popconfirm,
Space,
Table,
theme,
} from 'antd';
import { AnyObject } from 'antd/es/_util/type';
import { ColumnType } from 'antd/es/table';
import graphql from 'babel-plugin-relay/macro';
Expand Down Expand Up @@ -106,6 +116,7 @@ const ProjectResourcePolicyList: React.FC<
supportMaxVfolderCount && {
title: t('resourcePolicy.MaxVFolderCount'),
dataIndex: 'max_vfolder_count',
key: 'max_vfolder_count',
render: (text: ProjectResourcePolicies) =>
_.toNumber(text) === 0 ? '∞' : text,
sorter: (a, b) =>
Expand All @@ -118,6 +129,7 @@ const ProjectResourcePolicyList: React.FC<
supportMaxQuotaScopeSize && {
title: t('resourcePolicy.MaxQuotaScopeSize'),
dataIndex: 'max_quota_scope_size',
key: 'max_quota_scope_size',
render: (text) => (text === -1 ? '∞' : bytesToGB(text)),
sorter: (a, b) =>
numberSorterWithInfinityValue(
Expand All @@ -129,17 +141,20 @@ const ProjectResourcePolicyList: React.FC<
{
title: 'ID',
dataIndex: 'id',
key: 'id',
sorter: (a, b) => localeCompare(a?.id, b?.id),
},
{
title: t('resourcePolicy.CreatedAt'),
dataIndex: 'created_at',
key: 'created_at',
render: (text) => dayjs(text).format('lll'),
sorter: (a, b) => localeCompare(a?.created_at, b?.created_at),
},
{
title: t('general.Control'),
fixed: 'right',
key: 'control',
render: (text: any, row: ProjectResourcePolicies) => (
<Flex direction="row" align="stretch">
<Button
Expand Down Expand Up @@ -226,6 +241,30 @@ const ProjectResourcePolicyList: React.FC<
},
);

const handleExportCSV = () => {
if (!project_resource_policies) {
message.error(t('resourcePolicy.NoDataToExport'));
return;
}

const columnkeys = _.without(displayedColumnKeys, 'control');
const responseData = _.map(project_resource_policies, (policy) => {
return _.pick(
policy,
columnkeys.map((key) => key as keyof ProjectResourcePolicies),
);
});
exportCSVWithFormattingRules(
responseData as ProjectResourcePolicies[],
{
max_vfolder_count: (text: ProjectResourcePolicies) =>
_.toNumber(text) === 0 ? '-' : text,
max_quota_scope_size: (text) => (text === -1 ? '-' : bytesToGB(text)),
},
'project_resource_polices',
);
};

return (
<Flex direction="column" align="stretch">
<Flex
Expand All @@ -240,9 +279,30 @@ const ProjectResourcePolicyList: React.FC<
}}
>
<Flex direction="column" align="start">
<Typography.Text style={{ margin: 0, padding: 0 }}>
{t('resourcePolicy.ProjectResourcePolicy')}
</Typography.Text>
<Dropdown
menu={{
items: [
{
key: 'exportCSV',
label: t('resourcePolicy.ExportCSV'),
onClick: () => {
handleExportCSV();
},
},
],
}}
>
<Button
type="link"
style={{ padding: 0 }}
onClick={(e) => e.preventDefault()}
>
<Space style={{ color: token.colorLinkHover }}>
{t('resourcePolicy.Tools')}
<DownOutlined />
</Space>
</Button>
</Dropdown>
</Flex>
<Flex direction="row" gap={'xs'} wrap="wrap" style={{ flexShrink: 1 }}>
<Flex gap={'xs'}>
Expand Down
63 changes: 58 additions & 5 deletions react/src/components/UserResourcePolicyList.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
bytesToGB,
exportCSVWithFormattingRules,
filterEmptyItem,
localeCompare,
numberSorterWithInfinityValue,
Expand All @@ -16,12 +17,13 @@ import {
import { UserResourcePolicySettingModalFragment$key } from './__generated__/UserResourcePolicySettingModalFragment.graphql';
import {
DeleteOutlined,
DownOutlined,
PlusOutlined,
ReloadOutlined,
SettingOutlined,
} from '@ant-design/icons';
import { useLocalStorageState } from 'ahooks';
import { App, Button, Popconfirm, Table, theme, Typography } from 'antd';
import { App, Button, Dropdown, Popconfirm, Space, Table, theme } from 'antd';
import { AnyObject } from 'antd/es/_util/type';
import { ColumnType } from 'antd/es/table';
import graphql from 'babel-plugin-relay/macro';
Expand Down Expand Up @@ -92,7 +94,6 @@ const UserResourcePolicyList: React.FC<UserResourcePolicyListProps> = () => {
fetchKey: userResourcePolicyFetchKey,
},
);

const [commitDelete, isInflightDelete] =
useMutation<UserResourcePolicyListMutation>(graphql`
mutation UserResourcePolicyListMutation($name: String!) {
Expand All @@ -114,6 +115,7 @@ const UserResourcePolicyList: React.FC<UserResourcePolicyListProps> = () => {
supportMaxVfolderCount && {
title: t('resourcePolicy.MaxVFolderCount'),
dataIndex: 'max_vfolder_count',
key: 'max_vfolder_count',
render: (text) => (_.toNumber(text) === 0 ? '∞' : text),
sorter: (a, b) =>
numberSorterWithInfinityValue(
Expand All @@ -125,13 +127,15 @@ const UserResourcePolicyList: React.FC<UserResourcePolicyListProps> = () => {
supportMaxSessionCountPerModelSession && {
title: t('resourcePolicy.MaxSessionCountPerModelSession'),
dataIndex: 'max_session_count_per_model_session',
key: 'max_session_count_per_model_session',
sorter: (a, b) =>
(a?.max_session_count_per_model_session ?? 0) -
(b?.max_session_count_per_model_session ?? 0),
},
supportMaxQuotaScopeSize && {
title: t('resourcePolicy.MaxQuotaScopeSize'),
dataIndex: 'max_quota_scope_size',
key: 'max_quota_scope_size',
render: (text) => (text === -1 ? '∞' : bytesToGB(text)),
sorter: (a, b) =>
numberSorterWithInfinityValue(
Expand All @@ -142,6 +146,7 @@ const UserResourcePolicyList: React.FC<UserResourcePolicyListProps> = () => {
},
supportMaxCustomizedImageCount && {
title: t('resourcePolicy.MaxCustomizedImageCount'),
key: 'max_customized_image_count',
dataIndex: 'max_customized_image_count',
sorter: (a, b) =>
(a?.max_customized_image_count ?? 0) -
Expand All @@ -150,17 +155,20 @@ const UserResourcePolicyList: React.FC<UserResourcePolicyListProps> = () => {
{
title: 'ID',
dataIndex: 'id',
key: 'id',
sorter: (a, b) => localeCompare(a?.id, b?.id),
},
{
title: t('resourcePolicy.CreatedAt'),
dataIndex: 'created_at',
key: 'created_at',
render: (text) => dayjs(text).format('lll'),
sorter: (a, b) => localeCompare(a?.created_at, b?.created_at),
},
{
title: t('general.Control'),
fixed: 'right',
key: 'control',
render: (text: any, row: UserResourcePolicies) => (
<Flex direction="row" align="stretch">
<Button
Expand Down Expand Up @@ -244,6 +252,30 @@ const UserResourcePolicyList: React.FC<UserResourcePolicyListProps> = () => {
},
);

const handleExportCSV = () => {
if (!user_resource_policies || !displayedColumnKeys) {
message.error(t('resourcePolicy.NoDataToExport'));
return;
}

const columnKeys = _.without(displayedColumnKeys, 'control');
const responseData = _.map(user_resource_policies, (policy) => {
return _.pick(
policy,
columnKeys.map((key) => key as keyof UserResourcePolicies),
);
});

exportCSVWithFormattingRules(
responseData as UserResourcePolicies[],
{
max_vfolder_count: (text) => (_.toNumber(text) === 0 ? '-' : text),
max_quota_scope_size: (text) => (text === -1 ? '-' : bytesToGB(text)),
},
'user-resource-policies',
);
};

return (
<Flex direction="column" align="stretch">
<Flex
Expand All @@ -258,9 +290,30 @@ const UserResourcePolicyList: React.FC<UserResourcePolicyListProps> = () => {
}}
>
<Flex direction="column" align="start">
<Typography.Text style={{ margin: 0, padding: 0 }}>
{t('resourcePolicy.UserResourcePolicy')}
</Typography.Text>
<Dropdown
menu={{
items: [
{
key: 'exportCSV',
label: t('resourcePolicy.ExportCSV'),
onClick: () => {
handleExportCSV();
},
},
],
}}
>
<Button
type="link"
style={{ padding: 0 }}
onClick={(e) => e.preventDefault()}
>
<Space style={{ color: token.colorLinkHover }}>
{t('resourcePolicy.Tools')}
<DownOutlined />
</Space>
</Button>
</Dropdown>
</Flex>
<Flex direction="row" gap={'xs'} wrap="wrap" style={{ flexShrink: 1 }}>
<Flex gap={'xs'}>
Expand Down
Loading

0 comments on commit ab202d9

Please sign in to comment.