Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/large-chefs-fail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@wso2is/admin.console-settings.v1": patch
"@wso2is/admin.claims.v1": patch
"@wso2is/admin.users.v1": patch
"@wso2is/console": patch
---

Refactor immutable attribute configuration view and improve administrator listing in the console settings page

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,8 @@ export class ClaimManagementConstants {
*/
public static readonly USER_ID_CLAIM_URI: string = "http://wso2.org/claims/userid";
public static readonly USER_NAME_CLAIM_URI: string = "http://wso2.org/claims/username";
public static readonly CREATED_CLAIM_URI: string = "http://wso2.org/claims/created";
public static readonly MODIFIED_CLAIM_URI: string = "http://wso2.org/claims/modified";
public static readonly GROUPS_CLAIM_URI: string = "http://wso2.org/claims/groups";
public static readonly ROLES_CLAIM_URI: string = "http://wso2.org/claims/roles";
public static readonly APPLICATION_ROLES_CLAIM_URI: string = "http://wso2.org/claims/applicationRoles";
Expand Down
51 changes: 27 additions & 24 deletions features/admin.console-settings.v1/hooks/use-administrators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* under the License.
*/

import { UserBasicInterface, UserListInterface, UserRoleInterface } from "@wso2is/admin.core.v1/models/users";
import { UserBasicInterface, UserListInterface } from "@wso2is/admin.core.v1/models/users";
import { AppState } from "@wso2is/admin.core.v1/store";
import { SCIMConfigs } from "@wso2is/admin.extensions.v1/configs/scim";
import { useGetCurrentOrganizationType } from "@wso2is/admin.organizations.v1/hooks/use-get-organization-type";
Expand All @@ -26,13 +26,13 @@ import { useGetParentOrgUserInvites }
import { InvitationsInterface } from "@wso2is/admin.users.v1/components/guests/models/invite";
import { UserAccountTypes } from "@wso2is/admin.users.v1/constants/user-management-constants";
import { UserManagementUtils } from "@wso2is/admin.users.v1/utils/user-management-utils";
import { MultiValueAttributeInterface, RolesInterface } from "@wso2is/core/models";
import { MultiValueAttributeInterface } from "@wso2is/core/models";
import { AxiosError } from "axios";
import cloneDeep from "lodash-es/cloneDeep";
import isEmpty from "lodash-es/isEmpty";
import { useMemo, useState } from "react";
import { useSelector } from "react-redux";
import useConsoleRoles from "./use-console-roles";
import useConsoleSettings from "./use-console-settings";

/**
* Props interface of {@link UseAdministrators}
Expand Down Expand Up @@ -103,6 +103,24 @@ const useAdministrators = (

const { isSubOrganization } = useGetCurrentOrganizationType();

const { consoleId } = useConsoleSettings();

// Build the filter to fetch only users with console roles
const adminUsersFilter: string = useMemo(() => {
if (!consoleId) {
return null;
}

const roleFilter: string = `roles.audienceId eq ${consoleId}`;

// Combine with any additional filter if provided
if (filter && filter !== "") {
return `${roleFilter} and ${filter}`;
}

return roleFilter;
}, [ consoleId, filter ]);

const {
data: originalAdminUserList,
error: adminUserListFetchError,
Expand All @@ -111,20 +129,18 @@ const useAdministrators = (
} = useUsersList(
modifiedLimit,
startIndex + 1,
filter === "" ? null : filter,
adminUsersFilter,
attributes,
domain,
excludedAttributes,
shouldFetch
shouldFetch && !!consoleId
);

const {
data: invitedAdministrators,
mutate: mutateInvitedAdministratorsListFetchRequest
} = useGetParentOrgUserInvites(isSubOrganization());

const { consoleRoles } = useConsoleRoles(null, null);

/**
* Transform the original users list response from the API.
*
Expand All @@ -139,26 +155,13 @@ const useAdministrators = (
const clonedUserList: UserListInterface = cloneDeep(usersList);
const processedUserList: UserBasicInterface[] = [];

/**
* Checks whether administrator role is present in the user.
*/
const isAdminUser = (user: UserBasicInterface): boolean => {
return user?.roles?.some((userRole: UserRoleInterface) => {
return consoleRoles?.Resources?.some((consoleRole: RolesInterface) => {
return consoleRole.id === userRole.value;
});
});
};

const isOwner = (user: UserBasicInterface): boolean => {
return user[SCIMConfigs.scim.systemSchema]?.userAccountType === UserAccountTypes.OWNER;
};

clonedUserList.Resources = clonedUserList?.Resources?.map((resource: UserBasicInterface) => {
// Filter out users belong to groups named "Administrator"
if (!isAdminUser(resource)) {
return null;
}
// Since we're already filtering by console roles at the API level,
// all returned users are administrators. We just need to sort them properly.

if (isOwner(resource) && UserManagementUtils.isAuthenticatedUser(authenticatedUser, resource?.userName)) {
processedUserList[0] = resource;
Expand Down Expand Up @@ -234,12 +237,12 @@ const useAdministrators = (
};

const administrators: UserListInterface = useMemo(() => {
if (isEmpty(originalAdminUserList) || isEmpty(consoleRoles)) {
if (isEmpty(originalAdminUserList)) {
return {};
}

return transformUserList(originalAdminUserList);
}, [ originalAdminUserList, consoleRoles ]);
}, [ originalAdminUserList ]);

return {
adminUserListFetchError,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,13 @@ const UserProfileForm: FunctionComponent<UserProfileFormPropsInterface> = ({
return false;
}

// Filter out meta fields (userId, created, modified) as they are already rendered separately
if (schema.name === "id" ||
schema.name === "meta.created" ||
schema.name === "meta.lastModified") {
return false;
}

if (schema.schemaUri === ProfileConstants.SCIM2_CORE_USER_SCHEMA_ATTRIBUTES.emails &&
!commonExtensionConfig?.userEditSection?.showEmail) {
return false;
Expand Down Expand Up @@ -885,6 +892,33 @@ const UserProfileForm: FunctionComponent<UserProfileFormPropsInterface> = ({
return true;
};

/**
* Get the display name for a schema field.
* If useDefaultLabelsAndOrder is enabled, returns the translated fallback key.
* Otherwise, attempts to find the schema and return its displayName.
* Falls back to the translated key or schemaName if schema is not found.
*
* @param schemaName - The schema name to look up.
* @param fallbackI18nKey - Optional i18n key to use as fallback.
* @returns The display name for the schema field.
*/
const getSchemaDisplayName = (schemaName: string, fallbackI18nKey?: string): string => {

if (useDefaultLabelsAndOrder && fallbackI18nKey) {
return t(fallbackI18nKey);
}

const schema: ProfileSchemaInterface = flattenedProfileSchema.find(
(s: ProfileSchemaInterface) => s.name === schemaName
);

if (!schema) {
return fallbackI18nKey ? t(fallbackI18nKey) : schemaName;
}

return schema.displayName;
};

return (
<FinalForm
onSubmit={ handleSubmit }
Expand All @@ -906,7 +940,7 @@ const UserProfileForm: FunctionComponent<UserProfileFormPropsInterface> = ({
key="userID"
component={ TextFieldAdapter }
initialValue={ profileData?.id }
label={ t("user:profile.fields.userId") }
label={ getSchemaDisplayName("id", "user:profile.fields.userId") }
ariaLabel="userID"
name="userID"
type="text"
Expand Down Expand Up @@ -967,7 +1001,10 @@ const UserProfileForm: FunctionComponent<UserProfileFormPropsInterface> = ({
<Grid xs={ 12 }>
<TextField
defaultValue={ createdDate ? dayjs(createdDate).format("YYYY-MM-DD") : "" }
label={ t("user:profile.fields.createdDate") }
label={ getSchemaDisplayName(
"meta.created",
"user:profile.fields.createdDate"
) }
name="createdDate"
type="text"
InputProps={ {
Expand All @@ -987,7 +1024,10 @@ const UserProfileForm: FunctionComponent<UserProfileFormPropsInterface> = ({
<TextField
defaultValue={ modifiedDate
? dayjs(modifiedDate).format("YYYY-MM-DD") : "" }
label={ t("user:profile.fields.modifiedDate") }
label={ getSchemaDisplayName(
"meta.lastModified",
"user:profile.fields.modifiedDate"
) }
name="modifiedDate"
type="text"
InputProps={ {
Expand Down
39 changes: 37 additions & 2 deletions features/admin.users.v1/pages/users.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -598,13 +598,48 @@ const UsersPage: FunctionComponent<UsersPageInterface> = (
let filterAttributeOptions: DropdownChild[] = [
{
key: 0,
text: t("users:advancedSearch.form.dropdown." + "filterAttributeOptions.username"),
text: t("users:advancedSearch.form.dropdown.filterAttributeOptions.username"),
value: "userName"
}
];

// Add created time and modified time
const createdTimeOption: DropdownChild = {
key: "meta.created",
text: t("users:advancedSearch.form.dropdown.filterAttributeOptions.createdTime"),
value: "urn:ietf:params:scim:schemas:core:2.0:meta.created"
};

const modifiedTimeOption: DropdownChild = {
key: "meta.lastModified",
text: t("users:advancedSearch.form.dropdown.filterAttributeOptions.modifiedTime"),
value: "urn:ietf:params:scim:schemas:core:2.0:meta.lastModified"
};

const userIdOption: DropdownChild = {
key: "id",
text: t("users:advancedSearch.form.dropdown.filterAttributeOptions.userId"),
value: "urn:ietf:params:scim:schemas:core:2.0:id"
};

filterAttributeOptions.push(createdTimeOption);
filterAttributeOptions.push(modifiedTimeOption);
filterAttributeOptions.push(userIdOption);

if (useConsoleAttributeList) {
filterAttributeOptions = filterAttributeOptions.concat(userSearchAttributes);
// Filter out duplicates based on value
const existingValues: Set<string> = new Set(
filterAttributeOptions.map((option: DropdownChild) => option.value)
);

// Also exclude the SCIM format userName as it's already added
existingValues.add("urn:ietf:params:scim:schemas:core:2.0:User:userName");

const uniqueUserSearchAttributes: DropdownChild[] = userSearchAttributes.filter(
(option: DropdownChild) => !existingValues.has(option.value)
);

filterAttributeOptions = filterAttributeOptions.concat(uniqueUserSearchAttributes);
}

return filterAttributeOptions;
Expand Down
3 changes: 3 additions & 0 deletions modules/i18n/src/models/namespaces/users-ns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,10 @@ export interface usersNS {
form: {
dropdown: {
filterAttributeOptions: {
createdTime: string;
modifiedTime: string;
username: string;
userId: string;
email: string;
};
};
Expand Down
3 changes: 3 additions & 0 deletions modules/i18n/src/translations/en-US/portals/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ export const users: usersNS = {
form: {
dropdown: {
filterAttributeOptions: {
createdTime: "Created Time",
email: "Email",
modifiedTime: "Modified Time",
userId: "User ID",
username: "Username"
}
},
Expand Down
Loading