Skip to content

Commit 154487d

Browse files
Merge pull request #8101 from DonOmalVindula/fix/30299
Add support to dynamically display attributes in user creation wizard
2 parents 17e6766 + c800e90 commit 154487d

File tree

8 files changed

+1586
-91
lines changed

8 files changed

+1586
-91
lines changed

.changeset/mighty-toes-cross.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@wso2is/admin.users.v1": patch
3+
"@wso2is/core": patch
4+
"@wso2is/console": patch
5+
---
6+
7+
Add support to dynamically display attributes in user creation wizard

features/admin.users.v1/components/wizard/add-user-wizard.tsx

+88-50
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
*/
1818

1919
// Keep statement as this to avoid cyclic dependency. Do not import from config index.
20+
import { AppState } from "@wso2is/admin.core.v1/store";
2021
import { userConfig } from "@wso2is/admin.extensions.v1";
2122
import { administratorConfig } from "@wso2is/admin.extensions.v1/configs/administrator";
23+
import { SCIMConfigInterface } from "@wso2is/admin.extensions.v1/configs/models/scim";
2224
import { SCIMConfigs } from "@wso2is/admin.extensions.v1/configs/scim";
2325
import { userstoresConfig } from "@wso2is/admin.extensions.v1/configs/userstores";
2426
import { updateGroupDetails, useGroupList } from "@wso2is/admin.groups.v1/api/groups";
@@ -28,9 +30,12 @@ import useUserStores from "@wso2is/admin.userstores.v1/hooks/use-user-stores";
2830
import { UserStoreListItem } from "@wso2is/admin.userstores.v1/models";
2931
import { useValidationConfigData } from "@wso2is/admin.validation.v1/api";
3032
import { ValidationFormInterface } from "@wso2is/admin.validation.v1/models";
33+
import { isFeatureEnabled } from "@wso2is/core/helpers";
3134
import {
3235
AlertLevels,
36+
FeatureAccessConfigInterface,
3337
IdentifiableComponentInterface,
38+
MultiValueAttributeInterface,
3439
RolesInterface,
3540
TestableComponentInterface
3641
} from "@wso2is/core/models";
@@ -40,10 +45,12 @@ import { Heading, LinkButton, PrimaryButton, Steps, useWizardAlert } from "@wso2
4045
import { AxiosError, AxiosResponse } from "axios";
4146
import cloneDeep from "lodash-es/cloneDeep";
4247
import intersection from "lodash-es/intersection";
48+
import isEmpty from "lodash-es/isEmpty";
4349
import merge from "lodash-es/merge";
50+
import omit from "lodash-es/omit";
4451
import React, { FunctionComponent, ReactElement, useEffect, useMemo, useState } from "react";
4552
import { useTranslation } from "react-i18next";
46-
import { useDispatch } from "react-redux";
53+
import { useDispatch, useSelector } from "react-redux";
4754
import { Dispatch } from "redux";
4855
import { Grid, Icon, Modal } from "semantic-ui-react";
4956
import { AddUserUpdated } from "./steps/add-user-basic";
@@ -58,10 +65,11 @@ import {
5865
} from "../../constants";
5966
import {
6067
AddUserWizardStateInterface,
68+
EmailsInterface,
6169
PayloadInterface,
6270
UserDetailsInterface,
63-
WizardStepInterface,
64-
createEmptyUserDetails } from "../../models/user";
71+
WizardStepInterface
72+
} from "../../models/user";
6573
import { generatePassword, getConfiguration, getUsernameConfiguration } from "../../utils";
6674

6775
interface AddUserWizardPropsInterface extends IdentifiableComponentInterface, TestableComponentInterface {
@@ -124,6 +132,9 @@ export const AddUserWizard: FunctionComponent<AddUserWizardPropsInterface> = (
124132
const [ submitGroupList, setSubmitGroupList ] = useTrigger();
125133
const [ finishSubmit, setFinishSubmit ] = useTrigger();
126134

135+
const userFeatureConfig: FeatureAccessConfigInterface = useSelector(
136+
(state: AppState) => state.config.ui.features.users);
137+
127138
const [ partiallyCompletedStep, setPartiallyCompletedStep ] = useState<number>(undefined);
128139
const [ currentWizardStep, setCurrentWizardStep ] = useState<number>(currentStep);
129140
const [ wizardState, setWizardState ] = useState<WizardStateInterface>(undefined);
@@ -141,6 +152,11 @@ export const AddUserWizard: FunctionComponent<AddUserWizardPropsInterface> = (
141152
const [ submitStep, setSubmitStep ] = useState<WizardStepsFormTypes>(undefined);
142153
const [ selectedGroupsList, setSelectedGroupList ] = useState<GroupsInterface[]>([]);
143154

155+
const isAttributeProfileForUserCreationEnabled: boolean = isFeatureEnabled(
156+
userFeatureConfig,
157+
UserManagementConstants.ATTRIBUTE_PROFILES_FOR_USER_CREATION_FEATURE_FLAG
158+
);
159+
144160
const excludedAttributes: string = "members";
145161

146162
const {
@@ -444,62 +460,84 @@ export const AddUserWizard: FunctionComponent<AddUserWizardPropsInterface> = (
444460
username = userInfo.domain + "/" + userInfo.userName;
445461
}
446462

447-
let userDetails: UserDetailsInterface = createEmptyUserDetails();
463+
let userDetails: UserDetailsInterface = {
464+
emails: [
465+
{
466+
primary: true,
467+
value: userInfo.email
468+
}
469+
],
470+
name: {
471+
familyName: userInfo.lastName,
472+
givenName: userInfo.firstName
473+
},
474+
profileUrl: userInfo.profileUrl,
475+
userName: username
476+
};
477+
448478
const password: string = userInfo.newPassword;
449479

450480
// Users who get invited offline are also considered as password-based users.
451481
// They will be assigned a randomly generated temporary password.
452482
// Temporary password can be changed via the offline invite link.
453483
if (askPasswordFromUser) {
454-
userDetails = {
455-
emails: [
456-
{
457-
primary: true,
458-
value: userInfo.email
459-
}
460-
],
461-
name: {
462-
familyName: userInfo.lastName,
463-
givenName: userInfo.firstName
464-
},
465-
password: password,
466-
profileUrl: userInfo.profileUrl,
467-
userName: username
468-
};
484+
userDetails.password = password;
469485
} else if (isOfflineUser) {
470-
userDetails = {
471-
emails: [
472-
{
473-
primary: true,
474-
value: userInfo.email
475-
}
476-
],
477-
name: {
478-
familyName: userInfo.lastName,
479-
givenName: userInfo.firstName
480-
},
481-
password: generateRandomPassword(),
482-
profileUrl: userInfo.profileUrl,
483-
userName: username
484-
};
486+
userDetails.password = generateRandomPassword();
485487
} else {
486-
userDetails = {
487-
emails: [
488-
{
489-
primary: true,
490-
value: userInfo.email
491-
}
492-
],
493-
name: {
494-
familyName: userInfo.lastName,
495-
givenName: userInfo.firstName
496-
},
497-
profileUrl: userInfo.profileUrl,
498-
[ SCIMConfigs.scim.systemSchema ]: {
499-
askPassword: "true"
500-
},
501-
userName: username
488+
userDetails[ SCIMConfigs.scim.systemSchema ] = {
489+
askPassword: "true"
490+
};
491+
}
492+
493+
if (isAttributeProfileForUserCreationEnabled) {
494+
const mergedSCIMSchema: SCIMConfigInterface = {
495+
...userDetails[SCIMConfigs.scim.systemSchema],
496+
...userInfo[SCIMConfigs.scim.systemSchema]
502497
};
498+
499+
const combinedUserDetails: UserDetailsInterface = omit({
500+
...userInfo,
501+
...userDetails,
502+
[SCIMConfigs.scim.systemSchema]: mergedSCIMSchema
503+
}, "passwordOption", "newPassword", "userType", "domain", "firstName", "lastName");
504+
505+
// If email value is not present, use the emails value.
506+
if (isEmpty(userInfo?.email) && userInfo?.emails.length > 0) {
507+
delete combinedUserDetails.email;
508+
509+
// Primary will be the string value in the emails array.
510+
const primaryEmail: string = userInfo.emails.find(
511+
(subAttribute: (string | MultiValueAttributeInterface | EmailsInterface)) =>
512+
typeof subAttribute === "string") as string;
513+
514+
if (primaryEmail) {
515+
combinedUserDetails.emails = [
516+
{
517+
primary: true,
518+
value: primaryEmail
519+
},
520+
...userInfo?.emails
521+
];
522+
}
523+
else {
524+
// This means that the user has not provided any email value.
525+
// This is an invalid case. Therefore, we need to throw an error.
526+
dispatch(addAlert({
527+
description: t(
528+
"users:notifications.addUser.genericError.description"
529+
),
530+
level: AlertLevels.ERROR,
531+
message: t(
532+
"users:notifications.addUser.genericError.message"
533+
)
534+
}));
535+
536+
return;
537+
}
538+
}
539+
540+
userDetails = { ...combinedUserDetails };
503541
}
504542

505543
setIsSubmitting(true);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com).
3+
*
4+
* WSO2 LLC. licenses this file to you under the Apache License,
5+
* Version 2.0 (the "License"); you may not use this file except
6+
* in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing,
12+
* software distributed under the License is distributed on an
13+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
* KIND, either express or implied. See the License for the
15+
* specific language governing permissions and limitations
16+
* under the License.
17+
*/
18+
19+
.multi-value-table-data-row {
20+
.truncate {
21+
white-space: nowrap;
22+
overflow: hidden;
23+
text-overflow: ellipsis;
24+
max-width: 200px;
25+
display: inline-block;
26+
vertical-align: bottom;
27+
}
28+
}
29+
30+
.ui.form .multi-attribute-label {
31+
margin: 0 0 .28571429rem 0;
32+
}
33+
34+
.ui.form .multi-attribute-field {
35+
margin-bottom: 0;
36+
}

0 commit comments

Comments
 (0)