Skip to content

Commit 669644b

Browse files
committed
feat: enhance CSV export functionality for permission groups and user permissions with configuration dialogs
1 parent f42dede commit 669644b

File tree

8 files changed

+324
-38
lines changed

8 files changed

+324
-38
lines changed

src/Pages/GlobalConfigurations/Authorization/PermissionGroups/List/ExportPermissionGroupsToCsv.tsx

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { FILE_NAMES, GROUP_EXPORT_HEADER_ROW } from '../../../../../components/common/ExportToCsv/constants'
18-
import ExportToCsv from '../../../../../components/common/ExportToCsv/ExportToCsv'
17+
import { ExportToCsv } from '@devtron-labs/devtron-fe-common-lib'
18+
19+
import { GROUP_EXPORT_HEADER_ROW } from '../../../../../components/common/ExportToCsv/constants'
1920
import { useAuthorizationContext } from '../../AuthorizationProvider'
2021
import { getRoleFiltersToExport } from '../../utils'
21-
import { PermissionGroupListHeaderProps } from './types'
22+
import { GROUP_EXPORT_HEADERS } from './constants'
23+
import { ExportPermissionGroupDataType, PermissionGroupListHeaderProps } from './types'
2224

2325
const ExportPermissionGroupsToCsv = ({
2426
disabled,
@@ -29,7 +31,7 @@ const ExportPermissionGroupsToCsv = ({
2931
/**
3032
* Provides the list of permission groups which have access to devtron applications
3133
*/
32-
const getPermissionGroupDataToExport = async () => {
34+
const getPermissionGroupDataToExport = async (): Promise<ExportPermissionGroupDataType[]> => {
3335
const { permissionGroups } = await exportCsvPromise()
3436

3537
const groupsList = permissionGroups.reduce((_groupsList, _group) => {
@@ -44,7 +46,7 @@ const ExportPermissionGroupsToCsv = ({
4446
isRowAdded = true
4547
}
4648

47-
const _groupData = {
49+
const _groupData: ExportPermissionGroupDataType = {
4850
groupName: _group.name,
4951
groupId: _group.id,
5052
description: _group.description || '-',
@@ -75,11 +77,14 @@ const ExportPermissionGroupsToCsv = ({
7577
}
7678

7779
return (
78-
<ExportToCsv
80+
<ExportToCsv<keyof ExportPermissionGroupDataType>
7981
disabled={disabled}
8082
apiPromise={getPermissionGroupDataToExport}
81-
fileName={FILE_NAMES.Groups}
82-
showOnlyIcon
83+
fileName="Devtron Apps Permission Group"
84+
triggerElementConfig={{
85+
showOnlyIcon: true,
86+
}}
87+
headers={GROUP_EXPORT_HEADERS}
8388
/>
8489
)
8590
}

src/Pages/GlobalConfigurations/Authorization/PermissionGroups/List/constants.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,11 @@
1414
* limitations under the License.
1515
*/
1616

17+
import { ExportToCsvProps } from '@devtron-labs/devtron-fe-common-lib'
18+
1719
import { DEFAULT_SHIMMER_LOADING_TABLE_ROWS } from '../../../../../config'
1820
import { PermissionGroup } from '../../types'
21+
import { ExportPermissionGroupDataType } from './types'
1922

2023
export const permissionGroupLoading: PermissionGroup[] = Array.from(
2124
Array(DEFAULT_SHIMMER_LOADING_TABLE_ROWS).keys(),
@@ -29,3 +32,14 @@ export const permissionGroupLoading: PermissionGroup[] = Array.from(
2932
export enum SortableKeys {
3033
name = 'name',
3134
}
35+
36+
export const GROUP_EXPORT_HEADERS: ExportToCsvProps<keyof ExportPermissionGroupDataType>['headers'] = [
37+
{ label: 'Group Name', key: 'groupName' },
38+
{ label: 'Group ID', key: 'groupId' },
39+
{ label: 'Description', key: 'description' },
40+
{ label: 'Super admin', key: 'superAdmin' },
41+
{ label: 'Project', key: 'project' },
42+
{ label: 'Environment', key: 'environment' },
43+
{ label: 'Application', key: 'application' },
44+
{ label: 'Role', key: 'role' },
45+
]

src/Pages/GlobalConfigurations/Authorization/PermissionGroups/List/types.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,14 @@ export interface UserPermissionTableProps
5858
showPagination: boolean
5959
isActionsDisabled: boolean
6060
}
61+
62+
export interface ExportPermissionGroupDataType {
63+
groupName: string
64+
groupId: number
65+
description: string
66+
superAdmin: boolean
67+
project: string
68+
environment: string
69+
application: string
70+
role: string
71+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*
2+
* Copyright (c) 2024. Devtron Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { ChangeEvent, useEffect } from 'react'
18+
19+
import {
20+
Button,
21+
ButtonStyleType,
22+
ButtonVariantType,
23+
Checkbox,
24+
CHECKBOX_VALUE,
25+
ComponentSizeType,
26+
Icon,
27+
Tooltip,
28+
VisibleModal,
29+
} from '@devtron-labs/devtron-fe-common-lib'
30+
31+
import { ExportConfigurationDialogProps } from './types'
32+
33+
const ExportConfigurationDialog = ({
34+
selectedConfig,
35+
setSelectedConfig,
36+
initialConfig,
37+
exportConfiguration,
38+
proceed,
39+
}: ExportConfigurationDialogProps) => {
40+
const { title, options } = exportConfiguration
41+
42+
const handleConfigSelectionChange = (e: ChangeEvent<HTMLInputElement>) => {
43+
setSelectedConfig((prev) => ({
44+
...prev,
45+
[e.target.name]: e.target.checked,
46+
}))
47+
}
48+
49+
useEffect(
50+
() => () => {
51+
setSelectedConfig(initialConfig)
52+
},
53+
[],
54+
)
55+
56+
const handleProceedAction = () => {
57+
proceed(true)
58+
}
59+
60+
const handleCancelAction = () => {
61+
proceed(false)
62+
}
63+
64+
return (
65+
<VisibleModal className="export-to-csv-modal" data-testid="export-to-csv-modal">
66+
<div className="modal__body mt-40 p-0">
67+
<h2 className="cn-9 fw-6 fs-16 m-0 dc__border-bottom px-20 py-12">Export to CSV</h2>
68+
<div className="py-16 flex top left dc__overflow-auto mxh-350 px-12">
69+
<div className="fs-13 lh-20 flexbox-col dc__gap-8 flex-grow-1">
70+
<h3 className="fw-6 cn-9 m-0 fs-13 lh-20 dc__truncate px-8">{title}</h3>
71+
<div>
72+
{options.map(({ label, value, description }) => (
73+
<label
74+
className={`m-0 py-6 px-8 flex left dc__gap-8 ${description ? 'top' : ''} dc__hover-n50 br-4 cursor`}
75+
key={value}
76+
htmlFor={value}
77+
>
78+
<Checkbox
79+
value={CHECKBOX_VALUE.CHECKED}
80+
name={value}
81+
dataTestId={`check-${label}`}
82+
id={value}
83+
isChecked={selectedConfig[value] ?? false}
84+
onChange={handleConfigSelectionChange}
85+
rootClassName="m-0 w-20 h-20"
86+
/>
87+
<p className="m-0 flexbox-col">
88+
<Tooltip content={label}>
89+
<span className="fs-13 fw-4 lh-20 cn-9 dc__truncate">{label}</span>
90+
</Tooltip>
91+
{description && (
92+
<Tooltip content={description}>
93+
<span className="fs-12 fw-4 lh-18 cn-7 dc__ellipsis-right__2nd-line">
94+
{description}
95+
</span>
96+
</Tooltip>
97+
)}
98+
</p>
99+
</label>
100+
))}
101+
</div>
102+
</div>
103+
</div>
104+
<div className="flex right dc__gap-12 dc__border-top py-16 px-20">
105+
<Button
106+
variant={ButtonVariantType.secondary}
107+
size={ComponentSizeType.medium}
108+
style={ButtonStyleType.neutral}
109+
onClick={handleCancelAction}
110+
text="Cancel"
111+
dataTestId="cancel-export-csv-button"
112+
/>
113+
<Button
114+
size={ComponentSizeType.medium}
115+
onClick={handleProceedAction}
116+
text="Download"
117+
dataTestId="retry-export-csv-button"
118+
endIcon={<Icon name="ic-download" color={null} />}
119+
disabled={!Object.values(selectedConfig).some((value) => value)}
120+
/>
121+
</div>
122+
</div>
123+
</VisibleModal>
124+
)
125+
}
126+
127+
export default ExportConfigurationDialog

src/Pages/GlobalConfigurations/Authorization/UserPermissions/List/ExportUserPermissionsToCsv.tsx

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,18 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { getFormattedUTCTimeForExport, UserTypeToFetchType } from '@devtron-labs/devtron-fe-common-lib'
17+
import { useState } from 'react'
1818

19-
import { ExportToCsvProps } from '@Components/common/ExportToCsv/types'
19+
import { ExportToCsv, ExportToCsvProps, getFormattedUTCTimeForExport } from '@devtron-labs/devtron-fe-common-lib'
2020

2121
import { importComponentFromFELibrary } from '../../../../../components/common'
22-
import { FILE_NAMES, USER_EXPORT_HEADER_ROW } from '../../../../../components/common/ExportToCsv/constants'
23-
import ExportToCsv from '../../../../../components/common/ExportToCsv/ExportToCsv'
22+
import { USER_EXPORT_HEADER_ROW } from '../../../../../components/common/ExportToCsv/constants'
2423
import { useAuthorizationContext } from '../../AuthorizationProvider'
2524
import { getRoleFiltersToExport } from '../../utils'
2625
import { LAST_LOGIN_TIME_NULL_STATE } from '../constants'
27-
import { UserPermissionListHeaderProps } from './types'
26+
import { USER_EXPORT_HEADERS } from './constants'
27+
import ExportConfigurationDialog from './ExportConfigurationDialog'
28+
import { ExportConfigurationDialogProps, ExportUserPermissionCSVDataType, UserPermissionListHeaderProps } from './types'
2829

2930
const getStatusExportText = importComponentFromFELibrary('getStatusExportText', null, 'function')
3031
const getUserExportToCsvConfiguration = importComponentFromFELibrary(
@@ -39,12 +40,29 @@ const ExportUserPermissionsToCsv = ({
3940
getDataToExport,
4041
}: Pick<UserPermissionListHeaderProps, 'disabled' | 'getDataToExport'>) => {
4142
const { customRoles } = useAuthorizationContext()
43+
const exportConfiguration: ExportConfigurationDialogProps['exportConfiguration'] = getUserExportToCsvConfiguration
44+
? getUserExportToCsvConfiguration()
45+
: null
46+
47+
const getInitialSelectedConfig = (): ExportConfigurationDialogProps['selectedConfig'] =>
48+
exportConfiguration?.options.reduce<ExportConfigurationDialogProps['selectedConfig']>(
49+
(acc, { value }) => {
50+
acc[value] = true
51+
52+
return acc
53+
},
54+
{} as ExportConfigurationDialogProps['selectedConfig'],
55+
) ?? ({} as ExportConfigurationDialogProps['selectedConfig'])
56+
57+
const [selectedConfig, setSelectedConfig] = useState(getInitialSelectedConfig)
4258

4359
/**
4460
* Returns the list of users which have permission to devtron applications
4561
*/
46-
const getUsersDataToExport: ExportToCsvProps<UserTypeToFetchType>['apiPromise'] = async (selectedConfig) => {
47-
const { users } = await getDataToExport(selectedConfig)
62+
const getUsersDataToExport: ExportToCsvProps<keyof ExportUserPermissionCSVDataType>['apiPromise'] = async ({
63+
signal,
64+
}) => {
65+
const { users } = await getDataToExport(selectedConfig, signal)
4866
const userList = users.reduce((_usersList, _user) => {
4967
let isRowAdded = false
5068

@@ -59,7 +77,7 @@ const ExportUserPermissionsToCsv = ({
5977

6078
const updatedOn = getFormattedUTCTimeForExport(_user.updatedOn)
6179

62-
const _userData = {
80+
const _userData: ExportUserPermissionCSVDataType = {
6381
emailId: _user.emailId,
6482
userId: _user.id,
6583
...(showStatus
@@ -130,15 +148,32 @@ const ExportUserPermissionsToCsv = ({
130148
return userList
131149
}
132150

151+
const renderConfigurationModal: ExportToCsvProps<
152+
keyof ExportUserPermissionCSVDataType
153+
>['modalConfig']['renderCustomModal'] = (proceed) => (
154+
<ExportConfigurationDialog
155+
selectedConfig={selectedConfig}
156+
setSelectedConfig={setSelectedConfig}
157+
initialConfig={getInitialSelectedConfig()}
158+
exportConfiguration={exportConfiguration}
159+
proceed={proceed}
160+
/>
161+
)
162+
133163
return (
134-
<ExportToCsv
164+
<ExportToCsv<keyof ExportUserPermissionCSVDataType>
135165
disabled={disabled}
136166
apiPromise={getUsersDataToExport}
137-
fileName={FILE_NAMES.Users}
138-
showOnlyIcon
139-
{...(getUserExportToCsvConfiguration && {
140-
configuration: getUserExportToCsvConfiguration(),
141-
})}
167+
fileName="Devtron Apps Users Data"
168+
triggerElementConfig={{
169+
showOnlyIcon: true,
170+
}}
171+
headers={USER_EXPORT_HEADERS}
172+
modalConfig={
173+
getUserExportToCsvConfiguration
174+
? { renderCustomModal: renderConfigurationModal, hideDialog: false }
175+
: null
176+
}
142177
/>
143178
)
144179
}

src/Pages/GlobalConfigurations/Authorization/UserPermissions/List/UserPermissionList.component.tsx

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -108,18 +108,24 @@ const UserPermissionList = () => {
108108

109109
const showLoadingState = isLoading || getIsRequestAborted(error)
110110

111-
const getUserDataForExport: UserPermissionContainerProps['getUserDataForExport'] = (selectedConfig) =>
112-
getUserList({
113-
...filterConfig,
114-
showAll: true,
115-
offset: null,
116-
size: null,
117-
sortBy: UserListSortableKeys.email,
118-
sortOrder: SortingOrder.ASC,
119-
...(getUserTypeToFetchFromSelectedConfigOptions && {
120-
typeToFetch: getUserTypeToFetchFromSelectedConfigOptions(selectedConfig),
121-
}),
122-
})
111+
const getUserDataForExport: UserPermissionContainerProps['getUserDataForExport'] = (
112+
selectedConfig,
113+
signal: AbortSignal,
114+
) =>
115+
getUserList(
116+
{
117+
...filterConfig,
118+
showAll: true,
119+
offset: null,
120+
size: null,
121+
sortBy: UserListSortableKeys.email,
122+
sortOrder: SortingOrder.ASC,
123+
...(getUserTypeToFetchFromSelectedConfigOptions && {
124+
typeToFetch: getUserTypeToFetchFromSelectedConfigOptions(selectedConfig),
125+
}),
126+
},
127+
signal,
128+
)
123129

124130
const getSelectAllDialogStatus = () => {
125131
// Set to show the modal, the function is called only if there is an existing selection,

0 commit comments

Comments
 (0)