()
+
+ useEffect(() => {
+ const rolesArray: AppRole[] = []
+ data?.map((a) => rolesArray.push(...a.roles))
+ setAllRoles(rolesArray)
+ dispatch(setRolesToAdd([]))
+ }, [data, dispatch])
+
+ useEffect(
+ () =>
+ setIdps(
+ idpsData ? idpsData.filter((idp: IdentityProvider) => idp.enabled) : []
+ ),
+ [idpsData]
+ )
+
+ useEffect(() => {
+ uploadAPIRResponse &&
+ setTableErrorData({
+ head: [''],
+ body: [uploadAPIRResponse.errors],
+ })
+ }, [uploadAPIRResponse])
+
+ const handleSelectRole = (role: string, select: boolean) => {
+ const isSelected = roles.includes(role)
+ if (!isSelected && select) {
+ dispatch(setRolesToAdd([...roles, role]))
+ } else if (isSelected && !select) {
+ const oldRoles = [...roles]
+ oldRoles.splice(oldRoles.indexOf(role), 1)
+ dispatch(setRolesToAdd([...oldRoles]))
+ }
+ }
+
+ const renderDropArea = (props: DropAreaProps) => {
+ return
+ }
+
+ const handleAddUserAPICall = async (csvData: any) => {
+ try {
+ if (uploadedFile) {
+ let blob = new Blob([Papa.unparse(csvData)], { type: 'text/csv' })
+ let file = new File([blob], uploadedFile.name, { type: 'text/csv' })
+ const response = await addMutipleUsers({
+ identityProviderId: idps[0].identityProviderId,
+ csvFile: file,
+ }).unwrap()
+ setLoading(false)
+ setIsSuccess(true)
+ setUploadAPIRResponse(response)
+ }
+ } catch (error: any) {
+ setLoading(false)
+ setIsError(error.data.errors.document[0])
+ console.log(error)
+ }
+ }
+
+ const handleConfirm = async () => {
+ if (isSuccess || isError) dispatch(show(OVERLAYS.NONE, ''))
+ if (uploadedFile && !roles.length) setIsFileUploaded(true)
+ else if (uploadedFile && roles.length) {
+ setLoading(true)
+ uploadedFile &&
+ Papa.parse(uploadedFile, {
+ skipEmptyLines: true,
+ complete: async (results) => {
+ const csvData: any = results.data
+ csvData[0].push('Roles')
+ for (let i = 0; i < csvData.length; i++) {
+ if (i !== 0) csvData[i].push(roles.toString())
+ }
+ handleAddUserAPICall(csvData)
+ },
+ })
+ }
+ }
+
+ const onChangeFile = async (selectedFile: File) => {
+ setUploadedFile(selectedFile)
+
+ Papa.parse(selectedFile, {
+ skipEmptyLines: true,
+ complete: function (results) {
+ const csvData: any = results.data
+ setTotalRowsInCSV(csvData.length - 1)
+ },
+ })
+ }
+
+ const renderContent = () => {
+ if (isError) {
+ return (
+
+
+ {t('content.usermanagement.addMultipleUsers.error.note')}
+
+
+ {t('content.usermanagement.addMultipleUsers.error.description')}
+
+
+ {t('content.usermanagement.addMultipleUsers.error.details')}
+
+
+ {isError}
+
+
+ )
+ } else if (isSuccess) {
+ return (
+
+
+
+
+
+ {uploadAPIRResponse?.created}
+
+
+
+ {t(
+ 'content.usermanagement.addMultipleUsers.success.userUploaded'
+ )}
+
+
+
+
+
+ {uploadAPIRResponse?.error}
+
+
+
+ {t(
+ 'content.usermanagement.addMultipleUsers.success.userFailed'
+ )}
+
+
+
+
+
+
+ {t(
+ 'content.usermanagement.addMultipleUsers.success.errorUsersLabel'
+ )}
+
+
+
+ {tableErrorData && (
+
+ )}
+
+ )
+ } else if (isFileUploaded) {
+ return (
+
+
+ {t(
+ 'content.usermanagement.addMultipleUsers.uploadedFile.fileHeading'
+ )}
+
+
+
+
+
+ {uploadedFile?.name}
+
+
+
+
+
+ {totalRowsInCSV}
+
+
+
+ {t(
+ 'content.usermanagement.addMultipleUsers.uploadedFile.usersToBeUpload'
+ )}
+
+
+
+
+ {t(
+ 'content.usermanagement.addMultipleUsers.uploadedFile.addUserRolesHeading'
+ )}
+
+
+ {t('content.usermanagement.addMultipleUsers.uploadedFile.note')}
+
+
+ -
+
+ {t(
+ 'content.usermanagement.addMultipleUsers.uploadedFile.note1'
+ )}
+
+
+ -
+
+ {t(
+ 'content.usermanagement.addMultipleUsers.uploadedFile.note2'
+ )}
+
+
+ -
+
+ {t(
+ 'content.usermanagement.addMultipleUsers.uploadedFile.note3'
+ )}
+
+
+
+
+ {t(
+ 'content.usermanagement.addMultipleUsers.uploadedFile.availableRoles'
+ )}
+
+
+
+ {t('content.usermanagement.addMultipleUsers.uploadedFile.roleDesc')}
+
+ {allRoles.length > 0 ? (
+
+ {allRoles.map((role: AppRole) => (
+
+ handleSelectRole(role.role, e.target.checked)
+ }
+ />
+ ))}
+
+ ) : (
+
+ {t('content.addUserRight.noRolesFound')}
+
+ )}
+
+ )
+ } else {
+ return idps.length === 1 ? (
+
+
+
+
+ 2
+
+
+ {t('content.usermanagement.addMultipleUsers.step2.heading')}
+
+
+
+ {t('content.usermanagement.addMultipleUsers.step2.linkText')}
+
+
+
+
+
+
+ 3
+
+
+ {t('content.usermanagement.addMultipleUsers.step3.heading')}
+
+ {
+ file && onChangeFile(file)
+ }}
+ errorText={t(
+ 'content.usermanagement.addMultipleUsers.fileSizeError'
+ )}
+ DropStatusHeader={false}
+ DropArea={renderDropArea}
+ />
+
+
+ ) : (
+
+ )
+ }
+ }
+
+ return (
+ <>
+ dispatch(show(OVERLAYS.NONE, '')),
+ }}
+ />
+
+
+ {renderContent()}
+
+
+
+
+ {loading ? (
+ {}}
+ sx={{ marginLeft: '10px' }}
+ />
+ ) : (
+
+ )}
+
+ >
+ )
+}
diff --git a/src/components/pages/UserManagement/ActiveUserTable/index.tsx b/src/components/pages/UserManagement/ActiveUserTable/index.tsx
index 1034844ef..03d97d236 100644
--- a/src/components/pages/UserManagement/ActiveUserTable/index.tsx
+++ b/src/components/pages/UserManagement/ActiveUserTable/index.tsx
@@ -48,6 +48,12 @@ export const ActiveUserTable = ({
: ''
}
addButtonClick={() => dispatch(show(OVERLAYS.ADD_USER))}
+ addMultipleButtonLabel={
+ UserService.hasRole(ROLES.USERMANAGEMENT_ADD)
+ ? 'content.usermanagement.table.addMultiple'
+ : ''
+ }
+ onMultipleButtonClick={() => dispatch(show(OVERLAYS.ADD_MULTIPLE_USER))}
tableLabel={'content.usermanagement.table.title'}
fetchHook={useFetchUsersSearchQuery}
fetchHookArgs={{ expr, addUserResponse }}
diff --git a/src/components/shared/frame/UserList/index.tsx b/src/components/shared/frame/UserList/index.tsx
index f2bb7dd99..4e7c916ca 100644
--- a/src/components/shared/frame/UserList/index.tsx
+++ b/src/components/shared/frame/UserList/index.tsx
@@ -38,6 +38,8 @@ export const UserList = ({
sectionTitle,
addButtonLabel,
addButtonClick,
+ addMultipleButtonLabel,
+ onMultipleButtonClick,
tableLabel,
onDetailsClick,
fetchHook,
@@ -49,6 +51,8 @@ export const UserList = ({
sectionTitle: string
addButtonLabel: string
addButtonClick: () => void
+ addMultipleButtonLabel?: string
+ onMultipleButtonClick?: () => void
tableLabel: string
onDetailsClick: (row: TenantUser) => void
fetchHook: (paginArgs: PaginFetchArgs) => any
@@ -78,6 +82,8 @@ export const UserList = ({
onButtonClick={addButtonClick}
buttonLabel={t(addButtonLabel)}
+ secondButtonLabel={addMultipleButtonLabel && t(addMultipleButtonLabel)}
+ onSecondButtonClick={onMultipleButtonClick}
toolbarVariant="premium"
searchPlaceholder={t('global.table.search')}
columnHeadersBackgroundColor={'#FFFFFF'}
diff --git a/src/features/appManagement/userManagementApiSlice.ts b/src/features/appManagement/userManagementApiSlice.ts
new file mode 100644
index 000000000..a003316e4
--- /dev/null
+++ b/src/features/appManagement/userManagementApiSlice.ts
@@ -0,0 +1,57 @@
+/********************************************************************************
+ * Copyright (c) 2021, 2023 BMW Group AG
+ * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
+
+import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
+import { apiBaseQuery } from 'utils/rtkUtil'
+
+export type MultipleUsersRequest = {
+ identityProviderId: string
+ csvFile: File
+}
+
+export type MultipleUsersResponse = {
+ created: number
+ error: number
+ errors: string[]
+ total: number
+}
+
+export const apiSlice = createApi({
+ reducerPath: 'rtk/apps/appManagement',
+ baseQuery: fetchBaseQuery(apiBaseQuery()),
+ endpoints: (builder) => ({
+ addMutipleUsers: builder.mutation<
+ MultipleUsersResponse,
+ MultipleUsersRequest
+ >({
+ query: (data: MultipleUsersRequest) => {
+ const formData = new FormData()
+ formData.append('document', data.csvFile)
+ return {
+ url: 'api/administration/user/owncompany/usersfile',
+ method: 'POST',
+ body: formData,
+ }
+ },
+ }),
+ }),
+})
+
+export const { useAddMutipleUsersMutation } = apiSlice
diff --git a/src/features/store.ts b/src/features/store.ts
index 1f1444ce6..f6e1df903 100644
--- a/src/features/store.ts
+++ b/src/features/store.ts
@@ -63,6 +63,7 @@ import { apiSlice as serviceSubscriptionApiSlice } from './serviceSubscription/s
import { apiSlice as serviceAdminBoardApiSlice } from './adminBoard/serviceAdminBoardApiSlice'
import { apiSlice as companyRoleApiSlice } from './companyRoles/companyRoleApiSlice'
import { apiSlice as certificationApiSlice } from './certification/certificationApiSlice'
+import { apiSlice as userManagementApiSlice } from './appManagement/userManagementApiSlice'
import languageSlice from './language/slice'
import { apiSlice as usecaseApiSlice } from './usecase/usecaseApiSlice'
@@ -114,6 +115,7 @@ export const reducers = {
[serviceAdminBoardApiSlice.reducerPath]: serviceAdminBoardApiSlice.reducer,
[companyRoleApiSlice.reducerPath]: companyRoleApiSlice.reducer,
[certificationApiSlice.reducerPath]: certificationApiSlice.reducer,
+ [userManagementApiSlice.reducerPath]: userManagementApiSlice.reducer,
[usecaseApiSlice.reducerPath]: usecaseApiSlice.reducer,
}
@@ -143,6 +145,7 @@ export const store = configureStore({
.concat(serviceAdminBoardApiSlice.middleware)
.concat(companyRoleApiSlice.middleware)
.concat(certificationApiSlice.middleware)
+ .concat(userManagementApiSlice.middleware)
.concat(usecaseApiSlice.middleware),
})
diff --git a/src/services/AccessService.tsx b/src/services/AccessService.tsx
index 7954c5c0d..f6439a0e9 100644
--- a/src/services/AccessService.tsx
+++ b/src/services/AccessService.tsx
@@ -67,6 +67,7 @@ import AppDeclineAdminboard from 'components/overlays/DeclineAdminboard/AppDecli
import UpdateCompanyRole from 'components/overlays/UpdateCompanyRole'
import EditUsecase from 'components/overlays/EditUsecase'
import UpdateCertificate from 'components/overlays/UpdateCertificate'
+import AddMultipleUser from 'components/overlays/AddMultipleUser'
let pageMap: { [page: string]: IPage }
let actionMap: { [action: string]: IAction }
@@ -119,6 +120,8 @@ export const getOverlay = (overlay: OverlayState) => {
return null
case OVERLAYS.ADD_USER:
return
+ case OVERLAYS.ADD_MULTIPLE_USER:
+ return
case OVERLAYS.USER:
return
case OVERLAYS.TECHUSER:
diff --git a/src/types/Config.tsx b/src/types/Config.tsx
index 87fdaa9b2..9a5a4815f 100644
--- a/src/types/Config.tsx
+++ b/src/types/Config.tsx
@@ -500,6 +500,10 @@ export const ALL_OVERLAYS: IOverlay[] = [
name: OVERLAYS.ADD_USER,
role: ROLES.USERMANAGEMENT_ADD,
},
+ {
+ name: OVERLAYS.ADD_MULTIPLE_USER,
+ role: ROLES.USERMANAGEMENT_ADD,
+ },
{
name: OVERLAYS.ADD_TECHUSER,
role: ROLES.TECHUSER_ADD,
diff --git a/src/types/Constants.ts b/src/types/Constants.ts
index 684e4ab75..4b945f58f 100644
--- a/src/types/Constants.ts
+++ b/src/types/Constants.ts
@@ -103,6 +103,7 @@ export enum OVERLAYS {
NOT_FOUND = 'notfound',
NONE = 'none',
ADD_USER = 'add_user',
+ ADD_MULTIPLE_USER = 'add_multiple_user',
ADD_APP_USER_ROLES = 'add_app_user_roles',
EDIT_APP_USER_ROLES = 'edit_app_user_roles',
USER = 'user',
diff --git a/yarn.lock b/yarn.lock
index 0dfbabda0..0e2ede2c5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1175,10 +1175,10 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
-"@catena-x/portal-shared-components@^2.0.19":
- version "2.0.19"
- resolved "https://registry.yarnpkg.com/@catena-x/portal-shared-components/-/portal-shared-components-2.0.19.tgz#c3ba91421181e7b791379e80cdaf082beb46d201"
- integrity sha512-+WnZgUBYs487iCILfblZBb6G/bcrU2PqHH4D5MLx864oKKVUuE5pzYJtXuRe9sJYbK1MF6fq8lGmloclMjD5Jg==
+"@catena-x/portal-shared-components@^2.0.22":
+ version "2.0.22"
+ resolved "https://registry.yarnpkg.com/@catena-x/portal-shared-components/-/portal-shared-components-2.0.22.tgz#acfd9ed4b6c4d13c45d4ada3b8a2ca086fea7052"
+ integrity sha512-iKOxkfmsDK8yBjmwyKO0DqpRewBm5CVGSzhkC36zRuW9XZB6XBKuPcG49hCPkimVsR0zE91udID0QioAcHvGIA==
dependencies:
"@mui/base" "^5.0.0-beta.3"
"@mui/system" "^5.13.2"
@@ -2479,6 +2479,13 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.36.tgz#0db5d7efc4760d36d0d1d22c85d1a53accd5dc27"
integrity sha512-8egDX8dE50XyXWH6C6PRCNkTP106DuUrvdrednFouDSmCi7IOvrqr0frznfZaHifHH/3aq/7a7v9N4wdXMqhBQ==
+"@types/papaparse@^5.3.7":
+ version "5.3.7"
+ resolved "https://registry.yarnpkg.com/@types/papaparse/-/papaparse-5.3.7.tgz#8d3bf9e62ac2897df596f49d9ca59a15451aa247"
+ integrity sha512-f2HKmlnPdCvS0WI33WtCs5GD7X1cxzzS/aduaxSu3I7TbhWlENjSPs6z5TaB9K0J+BH1jbmqTaM+ja5puis4wg==
+ dependencies:
+ "@types/node" "*"
+
"@types/parse-json@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
@@ -7445,6 +7452,11 @@ p-try@^2.0.0:
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
+papaparse@^5.4.1:
+ version "5.4.1"
+ resolved "https://registry.yarnpkg.com/papaparse/-/papaparse-5.4.1.tgz#f45c0f871853578bd3a30f92d96fdcfb6ebea127"
+ integrity sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw==
+
param-case@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5"