Sort enabled features (#2557)
This commit is contained in:
parent
58112be510
commit
93088c5380
12 changed files with 200 additions and 143 deletions
|
@ -65,11 +65,11 @@ describe("Authentication test", () => {
|
|||
listingPage.goToItemDetails("Copy of browser");
|
||||
detailPage.addExecution(
|
||||
"Copy of browser forms",
|
||||
"console-username-password"
|
||||
"reset-credentials-choose-user"
|
||||
);
|
||||
|
||||
masthead.checkNotificationMessage("Flow successfully updated");
|
||||
detailPage.executionExists("Username Password Challenge");
|
||||
detailPage.executionExists("Choose User");
|
||||
});
|
||||
|
||||
it("should add a condition", () => {
|
||||
|
@ -80,7 +80,6 @@ describe("Authentication test", () => {
|
|||
);
|
||||
|
||||
masthead.checkNotificationMessage("Flow successfully updated");
|
||||
detailPage.executionExists("Username Password Challenge");
|
||||
});
|
||||
|
||||
it("should add a sub-flow", () => {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from "react";
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
Button,
|
||||
|
@ -9,10 +9,11 @@ import {
|
|||
PageSection,
|
||||
Radio,
|
||||
} from "@patternfly/react-core";
|
||||
|
||||
import type { AuthenticationProviderRepresentation } from "@keycloak/keycloak-admin-client/lib/defs/authenticatorConfigRepresentation";
|
||||
|
||||
import { PaginatingTableToolbar } from "../../../components/table-toolbar/PaginatingTableToolbar";
|
||||
import { useAdminClient, useFetch } from "../../../context/auth/AdminClient";
|
||||
import useLocaleSort, { mapByKey } from "../../../utils/useLocaleSort";
|
||||
import { providerConditionFilter } from "../../FlowDetails";
|
||||
|
||||
type AuthenticationProviderListProps = {
|
||||
|
@ -64,6 +65,7 @@ export const AddStepModal = ({ name, type, onSelect }: AddStepModalProps) => {
|
|||
useState<AuthenticationProviderRepresentation[]>();
|
||||
const [max, setMax] = useState(10);
|
||||
const [first, setFirst] = useState(0);
|
||||
const localeSort = useLocaleSort();
|
||||
|
||||
useFetch(
|
||||
async () => {
|
||||
|
@ -89,7 +91,14 @@ export const AddStepModal = ({ name, type, onSelect }: AddStepModalProps) => {
|
|||
[]
|
||||
);
|
||||
|
||||
const page = providers?.slice(first, first + max + 1);
|
||||
const page = useMemo(
|
||||
() =>
|
||||
localeSort(providers ?? [], mapByKey("displayName")).slice(
|
||||
first,
|
||||
first + max + 1
|
||||
),
|
||||
[providers, first, max]
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
@ -121,7 +130,7 @@ export const AddStepModal = ({ name, type, onSelect }: AddStepModalProps) => {
|
|||
>
|
||||
{providers && providers.length > max && (
|
||||
<PaginatingTableToolbar
|
||||
count={page?.length || 0}
|
||||
count={page.length || 0}
|
||||
first={first}
|
||||
max={max}
|
||||
onNextClick={setFirst}
|
||||
|
|
|
@ -20,6 +20,7 @@ import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable
|
|||
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||
import { useRealm } from "../context/realm-context/RealmContext";
|
||||
import { emptyFormatter } from "../util";
|
||||
import useLocaleSort, { mapByKey } from "../utils/useLocaleSort";
|
||||
import {
|
||||
CellDropdown,
|
||||
ClientScope,
|
||||
|
@ -32,9 +33,7 @@ import {
|
|||
import { ChangeTypeDropdown } from "./ChangeTypeDropdown";
|
||||
import { toNewClientScope } from "./routes/NewClientScope";
|
||||
|
||||
import "./client-scope.css";
|
||||
import { toClientScope } from "./routes/ClientScope";
|
||||
import { useWhoAmI } from "../context/whoami/WhoAmI";
|
||||
import {
|
||||
nameFilter,
|
||||
protocolFilter,
|
||||
|
@ -48,9 +47,10 @@ import type { Row } from "../clients/scopes/ClientScopes";
|
|||
import { getProtocolName } from "../clients/utils";
|
||||
import helpUrls from "../help-urls";
|
||||
|
||||
import "./client-scope.css";
|
||||
|
||||
export default function ClientScopesSection() {
|
||||
const { realm } = useRealm();
|
||||
const { whoAmI } = useWhoAmI();
|
||||
const { t } = useTranslation("client-scopes");
|
||||
|
||||
const adminClient = useAdminClient();
|
||||
|
@ -66,6 +66,7 @@ export default function ClientScopesSection() {
|
|||
AllClientScopes.none
|
||||
);
|
||||
const [searchProtocol, setSearchProtocol] = useState<ProtocolType>("all");
|
||||
const localeSort = useLocaleSort();
|
||||
|
||||
const [key, setKey] = useState(0);
|
||||
const refresh = () => {
|
||||
|
@ -87,7 +88,7 @@ export default function ClientScopesSection() {
|
|||
? typeFilter(searchTypeType)
|
||||
: protocolFilter(searchProtocol);
|
||||
|
||||
return clientScopes
|
||||
const transformed = clientScopes
|
||||
.map((scope) => {
|
||||
const row: Row = {
|
||||
...scope,
|
||||
|
@ -103,9 +104,12 @@ export default function ClientScopesSection() {
|
|||
};
|
||||
return row;
|
||||
})
|
||||
.filter(filter)
|
||||
.sort((a, b) => a.name!.localeCompare(b.name!, whoAmI.getLocale()))
|
||||
.slice(first, Number(first) + Number(max));
|
||||
.filter(filter);
|
||||
|
||||
return localeSort(transformed, mapByKey("name")).slice(
|
||||
first,
|
||||
Number(first) + Number(max)
|
||||
);
|
||||
};
|
||||
|
||||
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
||||
|
|
|
@ -19,9 +19,9 @@ import type ProtocolMapperRepresentation from "@keycloak/keycloak-admin-client/l
|
|||
import type { ProtocolMapperTypeRepresentation } from "@keycloak/keycloak-admin-client/lib/defs/serverInfoRepesentation";
|
||||
|
||||
import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
|
||||
import { useWhoAmI } from "../../context/whoami/WhoAmI";
|
||||
import { ListEmptyState } from "../../components/list-empty-state/ListEmptyState";
|
||||
import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable";
|
||||
import useLocaleSort, { mapByKey } from "../../utils/useLocaleSort";
|
||||
|
||||
type Row = {
|
||||
name: string;
|
||||
|
@ -46,28 +46,26 @@ export const AddMapperDialog = (props: AddMapperDialogProps) => {
|
|||
const { t } = useTranslation("client-scopes");
|
||||
|
||||
const serverInfo = useServerInfo();
|
||||
const { whoAmI } = useWhoAmI();
|
||||
const protocol = props.protocol;
|
||||
const protocolMappers = serverInfo.protocolMapperTypes![protocol];
|
||||
const builtInMappers = serverInfo.builtinProtocolMappers![protocol];
|
||||
const [filter, setFilter] = useState<ProtocolMapperRepresentation[]>([]);
|
||||
const [selectedRows, setSelectedRows] = useState<Row[]>([]);
|
||||
const localeSort = useLocaleSort();
|
||||
|
||||
const allRows = useMemo(
|
||||
() =>
|
||||
builtInMappers
|
||||
.sort((a, b) => a.name!.localeCompare(b.name!, whoAmI.getLocale()))
|
||||
.map((mapper) => {
|
||||
const mapperType = protocolMappers.filter(
|
||||
(type) => type.id === mapper.protocolMapper
|
||||
)[0];
|
||||
return {
|
||||
item: mapper,
|
||||
name: mapper.name!,
|
||||
description: mapperType.helpText,
|
||||
};
|
||||
}),
|
||||
[]
|
||||
localeSort(builtInMappers, mapByKey("name")).map((mapper) => {
|
||||
const mapperType = protocolMappers.filter(
|
||||
(type) => type.id === mapper.protocolMapper
|
||||
)[0];
|
||||
return {
|
||||
item: mapper,
|
||||
name: mapper.name!,
|
||||
description: mapperType.helpText,
|
||||
};
|
||||
}),
|
||||
[builtInMappers, protocolMappers]
|
||||
);
|
||||
const [rows, setRows] = useState(allRows);
|
||||
|
||||
|
@ -78,10 +76,7 @@ export const AddMapperDialog = (props: AddMapperDialogProps) => {
|
|||
}
|
||||
|
||||
const sortedProtocolMappers = useMemo(
|
||||
() =>
|
||||
protocolMappers.sort((a, b) =>
|
||||
a.name!.localeCompare(b.name!, whoAmI.getLocale())
|
||||
),
|
||||
() => localeSort(protocolMappers, mapByKey("name")),
|
||||
[protocolMappers]
|
||||
);
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ import type ClientScopeRepresentation from "@keycloak/keycloak-admin-client/lib/
|
|||
import { useAdminClient, useFetch } from "../../../context/auth/AdminClient";
|
||||
import { HelpItem } from "../../../components/help-enabler/HelpItem";
|
||||
import { AddScopeDialog } from "../../scopes/AddScopeDialog";
|
||||
import { useWhoAmI } from "../../../context/whoami/WhoAmI";
|
||||
import useLocaleSort, { mapByKey } from "../../../utils/useLocaleSort";
|
||||
|
||||
export type RequiredIdValue = {
|
||||
id: string;
|
||||
|
@ -41,7 +41,7 @@ export const ClientScope = () => {
|
|||
>([]);
|
||||
|
||||
const adminClient = useAdminClient();
|
||||
const { whoAmI } = useWhoAmI();
|
||||
const localeSort = useLocaleSort();
|
||||
|
||||
useFetch(
|
||||
() => adminClient.clientScopes.find(),
|
||||
|
@ -49,11 +49,7 @@ export const ClientScope = () => {
|
|||
setSelectedScopes(
|
||||
getValues("clientScopes").map((s) => scopes.find((c) => c.id === s.id)!)
|
||||
);
|
||||
setScopes(
|
||||
scopes.sort((a, b) =>
|
||||
a.name!.localeCompare(b.name!, whoAmI.getLocale())
|
||||
)
|
||||
);
|
||||
setScopes(localeSort(scopes, mapByKey("name")));
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
|
|
@ -36,7 +36,7 @@ import { ChangeTypeDropdown } from "../../client-scopes/ChangeTypeDropdown";
|
|||
|
||||
import { toDedicatedScope } from "../routes/DedicatedScopeDetails";
|
||||
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||
import { useWhoAmI } from "../../context/whoami/WhoAmI";
|
||||
import useLocaleSort, { mapByKey } from "../../utils/useLocaleSort";
|
||||
|
||||
import "./client-scopes.css";
|
||||
|
||||
|
@ -60,9 +60,9 @@ export const ClientScopes = ({
|
|||
}: ClientScopesProps) => {
|
||||
const { t } = useTranslation("clients");
|
||||
const adminClient = useAdminClient();
|
||||
const { whoAmI } = useWhoAmI();
|
||||
const { addAlert, addError } = useAlerts();
|
||||
const { realm } = useRealm();
|
||||
const localeSort = useLocaleSort();
|
||||
|
||||
const [searchType, setSearchType] = useState<SearchType>("name");
|
||||
|
||||
|
@ -116,13 +116,15 @@ export const ClientScopes = ({
|
|||
clientScopes
|
||||
.filter((scope) => !names.includes(scope.name))
|
||||
.filter((scope) => scope.protocol === protocol)
|
||||
.sort((a, b) => a.name!.localeCompare(b.name!, whoAmI.getLocale()))
|
||||
);
|
||||
|
||||
const filter =
|
||||
searchType === "name" ? nameFilter(search) : typeFilter(searchTypeType);
|
||||
const firstNum = Number(first);
|
||||
const page = rows.filter(filter).slice(firstNum, firstNum + Number(max));
|
||||
const page = localeSort(rows.filter(filter), mapByKey("name")).slice(
|
||||
firstNum,
|
||||
firstNum + Number(max)
|
||||
);
|
||||
if (firstNum === 0) {
|
||||
return [
|
||||
{
|
||||
|
|
|
@ -11,7 +11,7 @@ import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
|
|||
import type ClientScopeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientScopeRepresentation";
|
||||
import { useParams } from "react-router";
|
||||
import type { EditClientPolicyConditionParams } from "../../realm-settings/routes/EditCondition";
|
||||
import { useWhoAmI } from "../../context/whoami/WhoAmI";
|
||||
import useLocaleSort, { mapByKey } from "../../utils/useLocaleSort";
|
||||
|
||||
export const MultivaluedScopesComponent = ({
|
||||
defaultValue,
|
||||
|
@ -22,8 +22,8 @@ export const MultivaluedScopesComponent = ({
|
|||
const { control } = useFormContext();
|
||||
const { conditionName } = useParams<EditClientPolicyConditionParams>();
|
||||
const adminClient = useAdminClient();
|
||||
const { whoAmI } = useWhoAmI();
|
||||
const [open, setOpen] = useState(false);
|
||||
const localeSort = useLocaleSort();
|
||||
const [clientScopes, setClientScopes] = useState<ClientScopeRepresentation[]>(
|
||||
[]
|
||||
);
|
||||
|
@ -62,11 +62,12 @@ export const MultivaluedScopesComponent = ({
|
|||
<>
|
||||
{open && (
|
||||
<AddScopeDialog
|
||||
clientScopes={clientScopes
|
||||
.filter((scope) => !value.includes(scope.name!))
|
||||
.sort((a, b) =>
|
||||
a.name!.localeCompare(b.name!, whoAmI.getLocale())
|
||||
)}
|
||||
clientScopes={localeSort(
|
||||
clientScopes.filter(
|
||||
(scope) => !value.includes(scope.name!)
|
||||
),
|
||||
mapByKey("name")
|
||||
)}
|
||||
isClientScopesConditionType
|
||||
open={open}
|
||||
toggleDialog={() => setOpen(!open)}
|
||||
|
|
|
@ -26,7 +26,7 @@ import { useRealm } from "../../context/realm-context/RealmContext";
|
|||
import { toPermissionDetails } from "../../clients/routes/PermissionDetails";
|
||||
import { KeycloakSpinner } from "../../components/keycloak-spinner/KeycloakSpinner";
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
import { useWhoAmI } from "../../context/whoami/WhoAmI";
|
||||
import useLocaleSort from "../../utils/useLocaleSort";
|
||||
import { useConfirmDialog } from "../confirm-dialog/ConfirmDialog";
|
||||
|
||||
import "./permissions-tab.css";
|
||||
|
@ -43,9 +43,9 @@ export const PermissionsTab = ({ id, type }: PermissionsTabProps) => {
|
|||
const history = useHistory();
|
||||
const adminClient = useAdminClient();
|
||||
const { realm } = useRealm();
|
||||
const { whoAmI } = useWhoAmI();
|
||||
const [realmId, setRealmId] = useState("");
|
||||
const [permission, setPermission] = useState<ManagementPermissionReference>();
|
||||
const localeSort = useLocaleSort();
|
||||
|
||||
const togglePermissionEnabled = (enabled: boolean) => {
|
||||
switch (type) {
|
||||
|
@ -173,48 +173,47 @@ export const PermissionsTab = ({ id, type }: PermissionsTabProps) => {
|
|||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{Object.entries(permission.scopePermissions || {})
|
||||
.sort((a, b) =>
|
||||
a[0]!.localeCompare(b[0]!, whoAmI.getLocale())
|
||||
)
|
||||
.map(([name, id]) => (
|
||||
<Tr key={id}>
|
||||
<Td>
|
||||
<Link
|
||||
to={toPermissionDetails({
|
||||
realm,
|
||||
id: realmId,
|
||||
permissionType: "scope",
|
||||
permissionId: id,
|
||||
})}
|
||||
>
|
||||
{name}
|
||||
</Link>
|
||||
</Td>
|
||||
<Td>
|
||||
{t(`scopePermissions.${type}.${name}-description`)}
|
||||
</Td>
|
||||
<Td isActionCell>
|
||||
<ActionsColumn
|
||||
items={[
|
||||
{
|
||||
title: t("common:edit"),
|
||||
onClick() {
|
||||
history.push(
|
||||
toPermissionDetails({
|
||||
realm,
|
||||
id: realmId,
|
||||
permissionType: "scope",
|
||||
permissionId: id,
|
||||
})
|
||||
);
|
||||
},
|
||||
{localeSort(
|
||||
Object.entries(permission.scopePermissions || {}),
|
||||
([name]) => name
|
||||
).map(([name, id]) => (
|
||||
<Tr key={id}>
|
||||
<Td>
|
||||
<Link
|
||||
to={toPermissionDetails({
|
||||
realm,
|
||||
id: realmId,
|
||||
permissionType: "scope",
|
||||
permissionId: id,
|
||||
})}
|
||||
>
|
||||
{name}
|
||||
</Link>
|
||||
</Td>
|
||||
<Td>
|
||||
{t(`scopePermissions.${type}.${name}-description`)}
|
||||
</Td>
|
||||
<Td isActionCell>
|
||||
<ActionsColumn
|
||||
items={[
|
||||
{
|
||||
title: t("common:edit"),
|
||||
onClick() {
|
||||
history.push(
|
||||
toPermissionDetails({
|
||||
realm,
|
||||
id: realmId,
|
||||
permissionType: "scope",
|
||||
permissionId: id,
|
||||
})
|
||||
);
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</TableComposable>
|
||||
</CardBody>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React from "react";
|
||||
import React, { useMemo } from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { xor } from "lodash-es";
|
||||
|
@ -33,14 +33,15 @@ import { toUpperCase } from "../util";
|
|||
import { HelpItem } from "../components/help-enabler/HelpItem";
|
||||
import environment from "../environment";
|
||||
import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner";
|
||||
import useLocaleSort from "../utils/useLocaleSort";
|
||||
import {
|
||||
routableTab,
|
||||
RoutableTabs,
|
||||
} from "../components/routable-tabs/RoutableTabs";
|
||||
import { DashboardTab, toDashboard } from "./routes/Dashboard";
|
||||
import { ProviderInfo } from "./ProviderInfo";
|
||||
|
||||
import "./dashboard.css";
|
||||
import { ProviderInfo } from "./ProviderInfo";
|
||||
|
||||
const EmptyDashboard = () => {
|
||||
const { t } = useTranslation("dashboard");
|
||||
|
@ -70,11 +71,28 @@ const Dashboard = () => {
|
|||
const { realm } = useRealm();
|
||||
const serverInfo = useServerInfo();
|
||||
const history = useHistory();
|
||||
const localeSort = useLocaleSort();
|
||||
|
||||
const enabledFeatures = xor(
|
||||
serverInfo.profileInfo?.disabledFeatures,
|
||||
serverInfo.profileInfo?.experimentalFeatures,
|
||||
serverInfo.profileInfo?.previewFeatures
|
||||
const enabledFeatures = useMemo(
|
||||
() =>
|
||||
localeSort(
|
||||
xor(
|
||||
serverInfo.profileInfo?.disabledFeatures,
|
||||
serverInfo.profileInfo?.experimentalFeatures,
|
||||
serverInfo.profileInfo?.previewFeatures
|
||||
),
|
||||
(item) => item
|
||||
),
|
||||
[serverInfo.profileInfo]
|
||||
);
|
||||
|
||||
const disabledFeatures = useMemo(
|
||||
() =>
|
||||
localeSort(
|
||||
serverInfo.profileInfo?.disabledFeatures ?? [],
|
||||
(item) => item
|
||||
),
|
||||
[serverInfo.profileInfo]
|
||||
);
|
||||
|
||||
const isExperimentalFeature = (feature: string) =>
|
||||
|
@ -193,11 +211,9 @@ const Dashboard = () => {
|
|||
</DescriptionListTerm>
|
||||
<DescriptionListDescription>
|
||||
<List variant={ListVariant.inline}>
|
||||
{serverInfo.profileInfo?.disabledFeatures?.map(
|
||||
(feature) => (
|
||||
<ListItem key={feature}>{feature}</ListItem>
|
||||
)
|
||||
)}
|
||||
{disabledFeatures.map((feature) => (
|
||||
<ListItem key={feature}>{feature}</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</DescriptionListDescription>
|
||||
</DescriptionListGroup>
|
||||
|
|
|
@ -12,46 +12,47 @@ import type ComponentRepresentation from "@keycloak/keycloak-admin-client/lib/de
|
|||
import { KeycloakDataTable } from "../../../components/table-toolbar/KeycloakDataTable";
|
||||
import { ListEmptyState } from "../../../components/list-empty-state/ListEmptyState";
|
||||
import { useAlerts } from "../../../components/alert/Alerts";
|
||||
import { useAdminClient } from "../../../context/auth/AdminClient";
|
||||
import { useAdminClient, useFetch } from "../../../context/auth/AdminClient";
|
||||
import { useConfirmDialog } from "../../../components/confirm-dialog/ConfirmDialog";
|
||||
import { useWhoAmI } from "../../../context/whoami/WhoAmI";
|
||||
import useLocaleSort, { mapByKey } from "../../../utils/useLocaleSort";
|
||||
|
||||
export const LdapMapperList = () => {
|
||||
const history = useHistory();
|
||||
const { t } = useTranslation("user-federation");
|
||||
const adminClient = useAdminClient();
|
||||
const { addAlert, addError } = useAlerts();
|
||||
const { whoAmI } = useWhoAmI();
|
||||
const { url } = useRouteMatch();
|
||||
const [key, setKey] = useState(0);
|
||||
const refresh = () => setKey(key + 1);
|
||||
|
||||
const [mappers, setMappers] = useState<ComponentRepresentation[]>([]);
|
||||
const localeSort = useLocaleSort();
|
||||
|
||||
const { id } = useParams<{ id: string }>();
|
||||
|
||||
const [selectedMapper, setSelectedMapper] =
|
||||
useState<ComponentRepresentation>();
|
||||
|
||||
const loader = async () => {
|
||||
const testParams: {
|
||||
[name: string]: string | number;
|
||||
} = {
|
||||
parent: id,
|
||||
type: "org.keycloak.storage.ldap.mappers.LDAPStorageMapper",
|
||||
};
|
||||
|
||||
const mappersList = (await adminClient.components.find(testParams)).map(
|
||||
(mapper) => {
|
||||
return {
|
||||
...mapper,
|
||||
name: mapper.name,
|
||||
type: mapper.providerId,
|
||||
} as ComponentRepresentation;
|
||||
}
|
||||
);
|
||||
return mappersList.sort((a, b) =>
|
||||
a.name!.localeCompare(b.name!, whoAmI.getLocale())
|
||||
);
|
||||
};
|
||||
useFetch(
|
||||
() =>
|
||||
adminClient.components.find({
|
||||
parent: id,
|
||||
type: "org.keycloak.storage.ldap.mappers.LDAPStorageMapper",
|
||||
}),
|
||||
(mapper) => {
|
||||
setMappers(
|
||||
localeSort(
|
||||
mapper.map((mapper) => ({
|
||||
...mapper,
|
||||
name: mapper.name,
|
||||
type: mapper.providerId,
|
||||
})),
|
||||
mapByKey("name")
|
||||
)
|
||||
);
|
||||
},
|
||||
[key]
|
||||
);
|
||||
|
||||
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
||||
titleKey: t("common:deleteMappingTitle", { mapperId: selectedMapper?.id }),
|
||||
|
@ -88,7 +89,7 @@ export const LdapMapperList = () => {
|
|||
<DeleteConfirm />
|
||||
<KeycloakDataTable
|
||||
key={key}
|
||||
loader={loader}
|
||||
loader={mappers}
|
||||
ariaLabelKey="ldapMappersList"
|
||||
searchPlaceholderKey="common:searchForMapper"
|
||||
toolbarItem={
|
||||
|
|
|
@ -10,8 +10,8 @@ import {
|
|||
} from "@patternfly/react-core";
|
||||
|
||||
import type CredentialRepresentation from "@keycloak/keycloak-admin-client/lib/defs/credentialRepresentation";
|
||||
import { useWhoAmI } from "../../context/whoami/WhoAmI";
|
||||
import useToggle from "../../utils/useToggle";
|
||||
import useLocaleSort from "../../utils/useLocaleSort";
|
||||
import { CredentialDataDialog } from "./CredentialDataDialog";
|
||||
|
||||
type CredentialRowProps = {
|
||||
|
@ -30,8 +30,7 @@ export const CredentialRow = ({
|
|||
const { t } = useTranslation("users");
|
||||
const [showData, toggleShow] = useToggle();
|
||||
const [kebabOpen, toggleKebab] = useToggle();
|
||||
|
||||
const { whoAmI } = useWhoAmI();
|
||||
const localeSort = useLocaleSort();
|
||||
|
||||
const rows = useMemo(() => {
|
||||
if (!credential.credentialData) {
|
||||
|
@ -41,17 +40,15 @@ export const CredentialRow = ({
|
|||
const credentialData: Record<string, unknown> = JSON.parse(
|
||||
credential.credentialData
|
||||
);
|
||||
const locale = whoAmI.getLocale();
|
||||
return localeSort(Object.entries(credentialData), ([key]) => key).map<
|
||||
[string, string]
|
||||
>(([key, value]) => {
|
||||
if (typeof value === "string") {
|
||||
return [key, value];
|
||||
}
|
||||
|
||||
return Object.entries(credentialData)
|
||||
.sort(([a], [b]) => a.localeCompare(b, locale))
|
||||
.map<[string, string]>(([key, value]) => {
|
||||
if (typeof value === "string") {
|
||||
return [key, value];
|
||||
}
|
||||
|
||||
return [key, JSON.stringify(value)];
|
||||
});
|
||||
return [key, JSON.stringify(value)];
|
||||
});
|
||||
}, [credential.credentialData]);
|
||||
|
||||
return (
|
||||
|
|
38
src/utils/useLocaleSort.ts
Normal file
38
src/utils/useLocaleSort.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
import { useWhoAmI } from "../context/whoami/WhoAmI";
|
||||
|
||||
export type ValueMapperFn<T> = (item: T) => string | undefined;
|
||||
|
||||
export default function useLocaleSort() {
|
||||
const { whoAmI } = useWhoAmI();
|
||||
|
||||
return function localeSort<T>(items: T[], mapperFn: ValueMapperFn<T>): T[] {
|
||||
const locale = whoAmI.getLocale();
|
||||
|
||||
return [...items].sort((a, b) => {
|
||||
const valA = mapperFn(a);
|
||||
const valB = mapperFn(b);
|
||||
|
||||
if (valA === undefined || valB === undefined) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return valA.localeCompare(valB, locale);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: This might be built into TypeScript into future.
|
||||
// See: https://github.com/microsoft/TypeScript/issues/48992
|
||||
type KeysMatching<T, V> = {
|
||||
[K in keyof T]: T[K] extends V ? K : never;
|
||||
}[keyof T];
|
||||
|
||||
export const mapByKey =
|
||||
<
|
||||
T extends { [_ in K]?: string },
|
||||
K extends KeysMatching<T, string | undefined>
|
||||
>(
|
||||
key: K
|
||||
) =>
|
||||
(item: T) =>
|
||||
item[key];
|
Loading…
Reference in a new issue