Skip to content

Commit d5fb17a

Browse files
authored
Merge pull request #1613 from devtron-labs/feat/user-status-p2
feat: add support for bulk operations for users and permission groups
2 parents 4ee7597 + a3e6d49 commit d5fb17a

38 files changed

+1433
-337
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"private": true,
55
"homepage": "/dashboard",
66
"dependencies": {
7-
"@devtron-labs/devtron-fe-common-lib": "0.0.64",
7+
"@devtron-labs/devtron-fe-common-lib": "0.0.65",
88
"@esbuild-plugins/node-globals-polyfill": "0.2.3",
99
"@rjsf/core": "^5.13.3",
1010
"@rjsf/utils": "^5.13.3",
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import {
2+
DEFAULT_BASE_PAGE_SIZE,
3+
ErrorScreenNotAuthorized,
4+
ERROR_EMPTY_SCREEN,
5+
noop,
6+
Reload,
7+
TOAST_ACCESS_DENIED,
8+
} from '@devtron-labs/devtron-fe-common-lib'
9+
import React, { useRef } from 'react'
10+
import { importComponentFromFELibrary } from '../../../../../components/common'
11+
import { API_STATUS_CODES } from '../../../../../config'
12+
import { useAuthorizationContext } from '../../AuthorizationProvider'
13+
import FiltersEmptyState from '../../shared/components/FilterEmptyState/FilterEmptyState.component'
14+
import BulkSelectionActionWidget from '../../shared/components/BulkSelection/BulkSelectionActionWidget'
15+
import BulkSelectionModal from '../../shared/components/BulkSelection/BulkSelectionModal'
16+
import NoPermissionGroups from './NoPermissionGroups'
17+
import PermissionGroupListHeader from './PermissionGroupListHeader'
18+
import PermissionGroupTable from './PermissionGroupTable'
19+
import { PermissionGroupContainerProps } from './types'
20+
import { BulkSelectionModalTypes, useAuthorizationBulkSelection } from '../../shared/components/BulkSelection'
21+
import { BulkSelectionEntityTypes } from '../../shared/components/BulkSelection/constants'
22+
23+
const PermissionGroupInfoBar = importComponentFromFELibrary('PermissionGroupInfoBar', noop, 'function')
24+
25+
const PermissionGroupContainer = ({
26+
error,
27+
getPermissionGroupDataForExport,
28+
showLoadingState,
29+
totalCount,
30+
permissionGroups,
31+
refetchPermissionGroupList,
32+
urlFilters,
33+
bulkSelectionModalConfig,
34+
setBulkSelectionModalConfig,
35+
}: PermissionGroupContainerProps) => {
36+
const { isAutoAssignFlowEnabled } = useAuthorizationContext()
37+
38+
const isClearBulkSelectionModalOpen = !!bulkSelectionModalConfig?.type
39+
40+
const { searchKey, handleSearch: _handleSearch, clearFilters } = urlFilters
41+
42+
const draggableRef = useRef<HTMLDivElement>()
43+
const { getSelectedIdentifiersCount, isBulkSelectionApplied } = useAuthorizationBulkSelection()
44+
const isSomeRowChecked = getSelectedIdentifiersCount() > 0
45+
const selectedUsersCount = isBulkSelectionApplied ? totalCount : getSelectedIdentifiersCount()
46+
47+
if (!showLoadingState) {
48+
if (error) {
49+
if (error.code === API_STATUS_CODES.PERMISSION_DENIED) {
50+
return (
51+
<ErrorScreenNotAuthorized
52+
subtitle={ERROR_EMPTY_SCREEN.REQUIRED_MANAGER_ACCESS}
53+
title={TOAST_ACCESS_DENIED.TITLE}
54+
/>
55+
)
56+
}
57+
return <Reload reload={refetchPermissionGroupList} className="flex-grow-1" />
58+
}
59+
60+
// The null state is shown only when filters are not applied
61+
if (totalCount === 0 && !searchKey) {
62+
return <NoPermissionGroups />
63+
}
64+
}
65+
66+
// Disable the filter actions
67+
const isActionsDisabled = showLoadingState || !(totalCount && permissionGroups.length)
68+
69+
const showPagination = totalCount > DEFAULT_BASE_PAGE_SIZE
70+
71+
const confirmApplyFilter = () =>
72+
new Promise((resolve, reject) => {
73+
if (isBulkSelectionApplied) {
74+
setBulkSelectionModalConfig({
75+
type: BulkSelectionModalTypes.clearAllAcrossPages,
76+
onSuccess: () => resolve(null),
77+
onCancel: () => reject(),
78+
})
79+
} else {
80+
resolve(null)
81+
}
82+
})
83+
84+
const handleSearch = (text: string) => {
85+
confirmApplyFilter()
86+
.then(() => {
87+
_handleSearch(text)
88+
})
89+
.catch(noop)
90+
}
91+
92+
return (
93+
<>
94+
<div className="flexbox-col dc__gap-8 flex-grow-1" ref={draggableRef}>
95+
<PermissionGroupListHeader
96+
disabled={isActionsDisabled}
97+
handleSearch={handleSearch}
98+
initialSearchText={searchKey}
99+
getDataToExport={getPermissionGroupDataForExport}
100+
/>
101+
{isAutoAssignFlowEnabled && (
102+
<div className="pl-20 pr-20">
103+
<PermissionGroupInfoBar />
104+
</div>
105+
)}
106+
{showLoadingState || (totalCount && permissionGroups.length) ? (
107+
<PermissionGroupTable
108+
isLoading={showLoadingState}
109+
showPagination={showPagination}
110+
isActionsDisabled={isActionsDisabled}
111+
urlFilters={urlFilters}
112+
permissionGroups={permissionGroups}
113+
refetchPermissionGroupList={refetchPermissionGroupList}
114+
totalCount={totalCount}
115+
/>
116+
) : (
117+
<FiltersEmptyState clearFilters={clearFilters} />
118+
)}
119+
{isSomeRowChecked && selectedUsersCount > 0 && (
120+
<BulkSelectionActionWidget
121+
count={selectedUsersCount}
122+
parentRef={draggableRef}
123+
showStatus={false}
124+
areActionsDisabled={showLoadingState || isClearBulkSelectionModalOpen}
125+
setBulkSelectionModalConfig={setBulkSelectionModalConfig}
126+
refetchList={refetchPermissionGroupList}
127+
filterConfig={{
128+
searchKey: urlFilters.searchKey,
129+
}}
130+
selectedIdentifiersCount={selectedUsersCount}
131+
entityType={BulkSelectionEntityTypes.permissionGroups}
132+
/>
133+
)}
134+
</div>
135+
{isClearBulkSelectionModalOpen && (
136+
<BulkSelectionModal
137+
{...bulkSelectionModalConfig}
138+
refetchList={refetchPermissionGroupList}
139+
urlFilters={urlFilters}
140+
selectedIdentifiersCount={selectedUsersCount}
141+
setBulkSelectionModalConfig={setBulkSelectionModalConfig}
142+
entityType={BulkSelectionEntityTypes.permissionGroups}
143+
/>
144+
)}
145+
</>
146+
)
147+
}
148+
149+
export default PermissionGroupContainer

src/Pages/GlobalConfigurations/Authorization/PermissionGroups/List/PermissionGroupList.component.tsx

Lines changed: 47 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,27 @@
1-
import React, { useMemo, useRef } from 'react'
1+
import React, { useMemo, useRef, useState } from 'react'
22
import {
33
SortingOrder,
4-
SortableTableHeaderCell,
5-
ErrorScreenNotAuthorized,
6-
ERROR_EMPTY_SCREEN,
7-
noop,
8-
Pagination,
9-
Reload,
10-
TOAST_ACCESS_DENIED,
114
useAsync,
12-
DEFAULT_BASE_PAGE_SIZE,
135
useUrlFilters,
146
abortPreviousRequests,
157
getIsRequestAborted,
8+
SelectAllDialogStatus,
9+
BulkSelectionProvider,
10+
BulkSelectionIdentifiersType,
1611
} from '@devtron-labs/devtron-fe-common-lib'
17-
import { API_STATUS_CODES } from '../../../../../config'
1812

1913
import { getPermissionGroupList } from '../../authorization.service'
20-
import { permissionGroupLoading, SortableKeys } from './constants'
21-
import PermissionGroupListHeader from './PermissionGroupListHeader'
22-
import PermissionGroupRow from './PermissionGroupRow'
23-
import { useAuthorizationContext } from '../../AuthorizationProvider'
24-
import { importComponentFromFELibrary } from '../../../../../components/common'
25-
import FiltersEmptyState from '../../shared/components/FilterEmptyState/FilterEmptyState.component'
26-
import NoPermissionGroups from './NoPermissionGroups'
27-
28-
const PermissionGroupInfoBar = importComponentFromFELibrary('PermissionGroupInfoBar', noop, 'function')
14+
import { SortableKeys } from './constants'
15+
import { PermissionGroup } from '../../types'
16+
import PermissionGroupContainer from './PermissionGroupContainer'
17+
import { BulkSelectionModalConfig, BulkSelectionModalTypes } from '../../shared/components/BulkSelection'
2918

3019
const PermissionGroupList = () => {
31-
const {
32-
pageSize,
33-
offset,
34-
changePage,
35-
changePageSize,
36-
searchKey,
37-
handleSearch,
38-
sortBy,
39-
handleSorting,
40-
sortOrder,
41-
clearFilters,
42-
} = useUrlFilters<SortableKeys>({ initialSortKey: SortableKeys.name })
20+
const [bulkSelectionModalConfig, setBulkSelectionModalConfig] = useState<BulkSelectionModalConfig>({
21+
type: null,
22+
})
23+
const urlFilters = useUrlFilters<SortableKeys>({ initialSortKey: SortableKeys.name })
24+
const { pageSize, offset, searchKey, sortBy, sortOrder } = urlFilters
4325
const filterConfig = useMemo(
4426
() => ({
4527
size: pageSize,
@@ -58,8 +40,19 @@ const PermissionGroupList = () => {
5840
abortControllerRef,
5941
),
6042
[filterConfig],
43+
true,
44+
{
45+
resetOnChange: false,
46+
},
47+
)
48+
const allOnThisPageIdentifiers = useMemo(
49+
() =>
50+
result?.permissionGroups.reduce((acc, group) => {
51+
acc[group.id] = true
52+
return acc
53+
}, {}) ?? {},
54+
[result],
6155
)
62-
const { isAutoAssignFlowEnabled } = useAuthorizationContext()
6356

6457
const showLoadingState = isLoading || getIsRequestAborted(error)
6558

@@ -73,99 +66,33 @@ const PermissionGroupList = () => {
7366
sortOrder: SortingOrder.ASC,
7467
})
7568

76-
if (!showLoadingState) {
77-
if (error) {
78-
if (error.code === API_STATUS_CODES.PERMISSION_DENIED) {
79-
return (
80-
<ErrorScreenNotAuthorized
81-
subtitle={ERROR_EMPTY_SCREEN.REQUIRED_MANAGER_ACCESS}
82-
title={TOAST_ACCESS_DENIED.TITLE}
83-
/>
84-
)
85-
}
86-
return <Reload reload={reload} className="flex-grow-1" />
87-
}
88-
89-
// The null state is shown only when filters are not applied
90-
if (result.totalCount === 0 && !searchKey) {
91-
return <NoPermissionGroups />
92-
}
93-
}
94-
95-
// Disable the filter actions
96-
const isActionsDisabled = showLoadingState || !(result.totalCount && result.permissionGroups.length)
69+
const getSelectAllDialogStatus = () => {
70+
// Set to show the modal, the function is called only if there is an existing selection,
71+
// so the modal won't open if there is no selection
72+
setBulkSelectionModalConfig({
73+
type: BulkSelectionModalTypes.selectAllAcrossPages,
74+
})
9775

98-
const sortByName = () => {
99-
handleSorting(SortableKeys.name)
76+
return SelectAllDialogStatus.OPEN
10077
}
10178

10279
return (
103-
<div className="flexbox-col dc__gap-8 flex-grow-1">
104-
<PermissionGroupListHeader
105-
disabled={isActionsDisabled}
106-
handleSearch={handleSearch}
107-
initialSearchText={searchKey}
108-
getDataToExport={getPermissionGroupDataForExport}
80+
<BulkSelectionProvider<BulkSelectionIdentifiersType<Record<PermissionGroup['id'], boolean>>>
81+
identifiers={allOnThisPageIdentifiers}
82+
getSelectAllDialogStatus={getSelectAllDialogStatus}
83+
>
84+
<PermissionGroupContainer
85+
error={error}
86+
getPermissionGroupDataForExport={getPermissionGroupDataForExport}
87+
showLoadingState={showLoadingState}
88+
totalCount={result?.totalCount ?? 0}
89+
permissionGroups={result?.permissionGroups ?? []}
90+
refetchPermissionGroupList={reload}
91+
urlFilters={urlFilters}
92+
bulkSelectionModalConfig={bulkSelectionModalConfig}
93+
setBulkSelectionModalConfig={setBulkSelectionModalConfig}
10994
/>
110-
{isAutoAssignFlowEnabled && (
111-
<div className="pl-20 pr-20">
112-
<PermissionGroupInfoBar />
113-
</div>
114-
)}
115-
{isLoading || (result.totalCount && result.permissionGroups.length) ? (
116-
<div className="flexbox-col flex-grow-1">
117-
<div className="user-permission__header cn-7 fs-12 fw-6 lh-20 dc__uppercase pl-20 pr-20 dc__border-bottom dc__position-sticky dc__top-0 bcn-0 dc__zi-1">
118-
<span />
119-
<SortableTableHeaderCell
120-
title="Name"
121-
sortOrder={sortOrder}
122-
isSorted={sortBy === SortableKeys.name}
123-
triggerSorting={sortByName}
124-
disabled={isActionsDisabled}
125-
/>
126-
<span>Description</span>
127-
<span />
128-
</div>
129-
{showLoadingState ? (
130-
permissionGroupLoading.map((permissionGroup) => (
131-
<div
132-
className="user-permission__row pl-20 pr-20 show-shimmer-loading"
133-
key={`permission-group-list-${permissionGroup.id}`}
134-
>
135-
<span className="child child-shimmer-loading" />
136-
<span className="child child-shimmer-loading" />
137-
<span className="child child-shimmer-loading" />
138-
</div>
139-
))
140-
) : (
141-
<>
142-
<div className="fs-13 fw-4 lh-20 cn-9 flex-grow-1">
143-
{result.permissionGroups.map((permissionGroup, index) => (
144-
<PermissionGroupRow
145-
{...permissionGroup}
146-
index={index}
147-
key={`permission-group-${permissionGroup.id}`}
148-
refetchPermissionGroupList={reload}
149-
/>
150-
))}
151-
</div>
152-
{result.totalCount > DEFAULT_BASE_PAGE_SIZE && (
153-
<Pagination
154-
rootClassName="flex dc__content-space pl-20 pr-20 dc__border-top"
155-
size={result.totalCount}
156-
offset={offset}
157-
pageSize={pageSize}
158-
changePage={changePage}
159-
changePageSize={changePageSize}
160-
/>
161-
)}
162-
</>
163-
)}
164-
</div>
165-
) : (
166-
<FiltersEmptyState clearFilters={clearFilters} />
167-
)}
168-
</div>
95+
</BulkSelectionProvider>
16996
)
17097
}
17198

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ const PermissionGroupListHeader = ({
5757
<div className="flex dc__gap-8">
5858
<SearchBar
5959
inputProps={{
60-
placeholder: 'Search groups',
60+
placeholder: 'Search group',
6161
}}
6262
handleEnter={handleSearch}
6363
initialSearchText={initialSearchText}

0 commit comments

Comments
 (0)