add priority attribute when dragging data list elements (#947)

* fix conflict

* pr feedback from jon

* add key provider type to separate component

* remove log stmt

* remove spread syntax

* add default priority to all provider types

* remove sort by priority from providers tab

* add instruction tooltip

* correct list sorting order

* feedback from Jon wip

* more feedback from Jon

* update aria-describedby

* data list header and draggable button spacing

* Update src/realm-settings/RealmSettingsSection.tsx

Co-authored-by: Jon Koops <jonkoops@gmail.com>

Co-authored-by: Jon Koops <jonkoops@gmail.com>
This commit is contained in:
Jenny 2021-08-20 12:15:12 -04:00 committed by GitHub
parent 75fd4007d3
commit e653d87894
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 130 additions and 62 deletions

View file

@ -21,6 +21,7 @@ import { useAlerts } from "../components/alert/Alerts";
import type ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
import { HelpItem } from "../components/help-enabler/HelpItem";
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
import { KEY_PROVIDER_TYPE } from "../util";
type JavaKeystoreModalProps = {
providerType: string;
@ -45,7 +46,7 @@ JavaKeystoreModalProps) => {
useState(false);
const allComponentTypes =
serverInfo.componentTypes?.["org.keycloak.keys.KeyProvider"] ?? [];
serverInfo.componentTypes?.[KEY_PROVIDER_TYPE] ?? [];
const save = async (component: ComponentRepresentation) => {
try {
@ -53,7 +54,8 @@ JavaKeystoreModalProps) => {
...component,
parentId: component.parentId,
providerId: providerType,
providerType: "org.keycloak.keys.KeyProvider",
providerType: KEY_PROVIDER_TYPE,
config: { priority: ["0"] },
});
handleModalToggle();
addAlert(t("saveProviderSuccess"), AlertVariant.success);

View file

@ -23,6 +23,7 @@ import {
Toolbar,
ToolbarGroup,
ToolbarItem,
Tooltip,
} from "@patternfly/react-core";
import { SearchIcon } from "@patternfly/react-icons";
@ -43,6 +44,7 @@ import { HMACGeneratedModal } from "./key-providers/hmac-generated/HMACGenerated
import { ECDSAGeneratedModal } from "./key-providers/ecdsa-generated/ECDSAGeneratedModal";
import { RSAModal } from "./RSAModal";
import { RSAGeneratedModal } from "./key-providers/rsa-generated/RSAGeneratedModal";
import { KEY_PROVIDER_TYPE } from "../util";
type ComponentData = KeyMetadataRepresentation & {
id?: string;
@ -50,6 +52,7 @@ type ComponentData = KeyMetadataRepresentation & {
name?: string;
toggleHidden?: boolean;
config?: any;
parentId?: string;
};
type KeysTabInnerProps = {
@ -75,11 +78,9 @@ export const KeysTabInner = ({ components, refresh }: KeysTabInnerProps) => {
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const serverInfo = useServerInfo();
const providerTypes = serverInfo.componentTypes![
"org.keycloak.keys.KeyProvider"
].map((item) => item.id);
const itemIds = components.map((_, idx) => "data" + idx);
const providerTypes = (
serverInfo.componentTypes?.[KEY_PROVIDER_TYPE] ?? []
).map((item) => item.id);
const [itemOrder, setItemOrder] = useState<string[]>([]);
const [providerDropdownOpen, setProviderDropdownOpen] = useState(false);
@ -93,6 +94,7 @@ export const KeysTabInner = ({ components, refresh }: KeysTabInnerProps) => {
const [liveText, setLiveText] = useState("");
useEffect(() => {
const itemIds = components.map((component) => component.id!);
setItemOrder(["data", ...itemIds]);
}, [components, searchVal]);
@ -117,7 +119,7 @@ export const KeysTabInner = ({ components, refresh }: KeysTabInnerProps) => {
},
});
const onDragStart = (id: string) => {
const onDragStart = async (id: string) => {
setLiveText(t("common:onDragStart", { item: id }));
setId(id);
};
@ -130,9 +132,40 @@ export const KeysTabInner = ({ components, refresh }: KeysTabInnerProps) => {
setLiveText(t("common:onDragCancel"));
};
const onDragFinish = (itemOrder: string[]) => {
setItemOrder(["data", ...itemOrder.filter((i) => i !== "data")]);
const onDragFinish = async (itemOrder: string[]) => {
setItemOrder(itemOrder);
setLiveText(t("common:onDragFinish"));
const updateAll = components.map((component: ComponentData) => {
const componentToSave = { ...component };
delete componentToSave.providerDescription;
return adminClient.components.update(
{ id: component.id! },
{
...componentToSave,
config: {
priority: [
(
itemOrder.length -
itemOrder.indexOf(component.id!) +
100
).toString(),
],
},
}
);
});
try {
await Promise.all(updateAll);
refresh();
addAlert(
t("realm-settings:saveProviderListSuccess"),
AlertVariant.success
);
} catch (error) {
addError("realm-settings:saveProviderError", error);
}
};
const onSearch = () => {
@ -278,7 +311,6 @@ export const KeysTabInner = ({ components, refresh }: KeysTabInnerProps) => {
</ToolbarGroup>
</>
</Toolbar>
<DataList
aria-label={t("groups")}
onDragFinish={onDragFinish}
@ -289,25 +321,27 @@ export const KeysTabInner = ({ components, refresh }: KeysTabInnerProps) => {
isCompact
>
<DataListItem aria-labelledby={"aria"} id="data" key="data">
<DataListItemRow className="test" data-testid={"data-list-row"}>
<DataListItemRow className="test" data-testid="data-list-row">
<DataListDragButton
className="header-drag-button"
aria-label="Reorder"
aria-labelledby="simple-item"
aria-describedby="Press space or enter to begin dragging, and use the arrow keys to navigate up or down. Press enter to confirm the drag, or any other key to cancel the drag operation."
aria-describedby={t("common-help:dragHelp")}
aria-pressed="false"
isDisabled
/>
<DataListItemCells
className="data-list-cells"
dataListCells={[
<DataListCell className="name" key={"1"}>
<DataListCell className="name" key="name">
<>{t("realm-settings:name")}</>
</DataListCell>,
<DataListCell className="provider" key={"2"}>
<DataListCell className="provider" key="provider">
<>{t("realm-settings:provider")}</>
</DataListCell>,
<DataListCell className="provider-description" key={"3"}>
<DataListCell
className="provider-description"
key="provider-description"
>
<>{t("realm-settings:providerDescription")}</>
</DataListCell>,
]}
@ -317,22 +351,23 @@ export const KeysTabInner = ({ components, refresh }: KeysTabInnerProps) => {
{(filteredComponents.length === 0
? components
: filteredComponents
).map((component: ComponentData, idx) => (
).map((component, idx) => (
<DataListItem
draggable
aria-labelledby={"aria"}
key={`data${idx}`}
id={`data${idx}`}
key={component.id}
id={component.id}
>
<DataListItemRow key={idx} data-testid={"data-list-row"}>
<DataListItemRow data-testid="data-list-row">
<DataListControl>
<Tooltip content={t("dragInstruction")} position="top">
<DataListDragButton
className="row-drag-button"
className="kc-row-drag-button"
aria-label="Reorder"
aria-labelledby="simple-item2"
aria-describedby="Press space or enter to begin dragging, and use the arrow keys to navigate up or down. Press enter to confirm the drag, or any other key to cancel the drag operation."
aria-describedby={t("common-help:dragHelp")}
aria-pressed="false"
/>
</Tooltip>
</DataListControl>
<DataListItemCells
dataListCells={[

View file

@ -21,6 +21,7 @@ import { useAlerts } from "../components/alert/Alerts";
import type ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
import { HelpItem } from "../components/help-enabler/HelpItem";
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
import { KEY_PROVIDER_TYPE } from "../util";
type RSAGeneratedModalProps = {
providerType: string;
@ -44,7 +45,7 @@ export const RSAGeneratedModal = ({
const [isRSAalgDropdownOpen, setIsRSAalgDropdownOpen] = useState(false);
const allComponentTypes =
serverInfo.componentTypes?.["org.keycloak.keys.KeyProvider"] ?? [];
serverInfo.componentTypes?.[KEY_PROVIDER_TYPE] ?? [];
const save = async (component: ComponentRepresentation) => {
try {
@ -52,7 +53,8 @@ export const RSAGeneratedModal = ({
...component,
parentId: component.parentId,
providerId: providerType,
providerType: "org.keycloak.keys.KeyProvider",
providerType: KEY_PROVIDER_TYPE,
config: { priority: ["0"] },
});
handleModalToggle();
addAlert(t("saveProviderSuccess"), AlertVariant.success);

View file

@ -23,6 +23,7 @@ import type ComponentRepresentation from "keycloak-admin/lib/defs/componentRepre
import { HelpItem } from "../components/help-enabler/HelpItem";
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
import { useParams } from "react-router-dom";
import { KEY_PROVIDER_TYPE } from "../util";
type RSAModalProps = {
providerType: string;
@ -50,7 +51,7 @@ export const RSAModal = ({
const [certificateFileName, setCertificateFileName] = useState("");
const allComponentTypes =
serverInfo.componentTypes?.["org.keycloak.keys.KeyProvider"] ?? [];
serverInfo.componentTypes?.[KEY_PROVIDER_TYPE] ?? [];
const save = async (component: ComponentRepresentation) => {
try {
@ -61,7 +62,7 @@ export const RSAModal = ({
...component,
parentId: component.parentId,
providerId: providerType,
providerType: "org.keycloak.keys.KeyProvider",
providerType: KEY_PROVIDER_TYPE,
}
);
addAlert(t("saveProviderSuccess"), AlertVariant.success);
@ -70,7 +71,8 @@ export const RSAModal = ({
...component,
parentId: component.parentId,
providerId: providerType,
providerType: "org.keycloak.keys.KeyProvider",
providerType: KEY_PROVIDER_TYPE,
config: { priority: ["0"] },
});
handleModalToggle();
addAlert(t("saveProviderSuccess"), AlertVariant.success);

View file

@ -172,3 +172,7 @@ article.pf-c-card.pf-m-flat.kc-login-settings-template
font-size: var(--pf-global--FontSize--md);
font-weight: bold;
}
.kc-row-drag-button {
padding: var(--pf-global--spacer--sm);
}

View file

@ -26,7 +26,7 @@ import { useRealm } from "../context/realm-context/RealmContext";
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
import { LocalizationTab } from "./LocalizationTab";
import { useWhoAmI } from "../context/whoami/WhoAmI";
import { toUpperCase } from "../util";
import { KEY_PROVIDER_TYPE, toUpperCase } from "../util";
import { RealmSettingsEmailTab } from "./EmailTab";
import { EventsTab } from "./event-config/EventsTab";
import { RealmSettingsGeneralTab } from "./GeneralTab";
@ -149,6 +149,19 @@ const RealmSettingsHeader = ({
);
};
const sortByPriority = (components: ComponentRepresentation[]) => {
const sortedComponents = [...components].sort((a, b) => {
const priorityA = Number(a.config?.priority);
const priorityB = Number(b.config?.priority);
return (
(!isNaN(priorityB) ? priorityB : 0) - (!isNaN(priorityA) ? priorityA : 0)
);
});
return sortedComponents;
};
export const RealmSettingsSection = () => {
const { t } = useTranslation("realm-settings");
const adminClient = useAdminClient();
@ -165,13 +178,13 @@ export const RealmSettingsSection = () => {
const { whoAmI } = useWhoAmI();
const kpComponentTypes =
useServerInfo().componentTypes!["org.keycloak.keys.KeyProvider"];
useServerInfo().componentTypes?.[KEY_PROVIDER_TYPE] ?? [];
useFetch(
async () => {
const realm = await adminClient.realms.findOne({ realm: realmName });
const realmComponents = await adminClient.components.find({
type: "org.keycloak.keys.KeyProvider",
type: KEY_PROVIDER_TYPE,
realm: realmName,
});
const user = await adminClient.users.findOne({ id: whoAmI.getUserId()! });
@ -179,7 +192,7 @@ export const RealmSettingsSection = () => {
return { user, realm, realmComponents };
},
({ user, realm, realmComponents }) => {
setRealmComponents(realmComponents);
setRealmComponents(sortByPriority(realmComponents));
setCurrentUser(user);
setRealm(realm);
},

View file

@ -22,7 +22,7 @@ import { useAdminClient, useFetch } from "../../../context/auth/AdminClient";
import { useParams, useRouteMatch } from "react-router-dom";
import { FormAccess } from "../../../components/form-access/FormAccess";
import { ViewHeader } from "../../../components/view-header/ViewHeader";
import { convertToFormValues } from "../../../util";
import { convertToFormValues, KEY_PROVIDER_TYPE } from "../../../util";
import { useAlerts } from "../../../components/alert/Alerts";
type AESGeneratedFormProps = {
@ -63,7 +63,7 @@ export const AESGeneratedForm = ({
...component,
parentId: component.parentId,
providerId: providerType,
providerType: "org.keycloak.keys.KeyProvider",
providerType: KEY_PROVIDER_TYPE,
}
);
addAlert(t("saveProviderSuccess"), AlertVariant.success);
@ -72,7 +72,8 @@ export const AESGeneratedForm = ({
...component,
parentId: component.parentId,
providerId: providerType,
providerType: "org.keycloak.keys.KeyProvider",
providerType: KEY_PROVIDER_TYPE,
config: { priority: ["0"] },
});
handleModalToggle?.();
addAlert(t("saveProviderSuccess"), AlertVariant.success);
@ -116,7 +117,7 @@ export const AESGeneratedForm = ({
);
const allComponentTypes =
serverInfo.componentTypes?.["org.keycloak.keys.KeyProvider"] ?? [];
serverInfo.componentTypes?.[KEY_PROVIDER_TYPE] ?? [];
const aesSecretSizeOptions = allComponentTypes[0].properties[3].options;

View file

@ -22,7 +22,7 @@ import { useAdminClient, useFetch } from "../../../context/auth/AdminClient";
import { useParams, useRouteMatch } from "react-router-dom";
import { FormAccess } from "../../../components/form-access/FormAccess";
import { ViewHeader } from "../../../components/view-header/ViewHeader";
import { convertToFormValues } from "../../../util";
import { convertToFormValues, KEY_PROVIDER_TYPE } from "../../../util";
import { useAlerts } from "../../../components/alert/Alerts";
type ECDSAGeneratedFormProps = {
@ -63,7 +63,7 @@ export const ECDSAGeneratedForm = ({
...component,
parentId: component.parentId,
providerId: providerType,
providerType: "org.keycloak.keys.KeyProvider",
providerType: KEY_PROVIDER_TYPE,
}
);
addAlert(t("saveProviderSuccess"), AlertVariant.success);
@ -72,7 +72,8 @@ export const ECDSAGeneratedForm = ({
...component,
parentId: component.parentId,
providerId: providerType,
providerType: "org.keycloak.keys.KeyProvider",
providerType: KEY_PROVIDER_TYPE,
config: { priority: ["0"] },
});
handleModalToggle?.();
addAlert(t("saveProviderSuccess"), AlertVariant.success);
@ -116,7 +117,7 @@ export const ECDSAGeneratedForm = ({
);
const allComponentTypes =
serverInfo.componentTypes?.["org.keycloak.keys.KeyProvider"] ?? [];
serverInfo.componentTypes?.[KEY_PROVIDER_TYPE] ?? [];
const ecdsaEllipticCurveOptions = allComponentTypes[1].properties[3].options;

View file

@ -22,7 +22,7 @@ import { useAdminClient, useFetch } from "../../../context/auth/AdminClient";
import { useParams, useRouteMatch } from "react-router-dom";
import { FormAccess } from "../../../components/form-access/FormAccess";
import { ViewHeader } from "../../../components/view-header/ViewHeader";
import { convertToFormValues } from "../../../util";
import { convertToFormValues, KEY_PROVIDER_TYPE } from "../../../util";
import { useAlerts } from "../../../components/alert/Alerts";
type HMACGeneratedFormProps = {
@ -65,7 +65,7 @@ export const HMACGeneratedForm = ({
...component,
parentId: component.parentId,
providerId: providerType,
providerType: "org.keycloak.keys.KeyProvider",
providerType: KEY_PROVIDER_TYPE,
}
);
addAlert(t("saveProviderSuccess"), AlertVariant.success);
@ -74,7 +74,8 @@ export const HMACGeneratedForm = ({
...component,
parentId: component.parentId,
providerId: providerType,
providerType: "org.keycloak.keys.KeyProvider",
providerType: KEY_PROVIDER_TYPE,
config: { priority: ["0"] },
});
handleModalToggle?.();
addAlert(t("saveProviderSuccess"), AlertVariant.success);
@ -121,7 +122,7 @@ export const HMACGeneratedForm = ({
);
const allComponentTypes =
serverInfo.componentTypes?.["org.keycloak.keys.KeyProvider"] ?? [];
serverInfo.componentTypes?.[KEY_PROVIDER_TYPE] ?? [];
const hmacSecretSizeOptions = allComponentTypes[2].properties[3].options;

View file

@ -22,7 +22,7 @@ import { useAdminClient, useFetch } from "../../../context/auth/AdminClient";
import { useParams, useRouteMatch } from "react-router-dom";
import { FormAccess } from "../../../components/form-access/FormAccess";
import { ViewHeader } from "../../../components/view-header/ViewHeader";
import { convertToFormValues } from "../../../util";
import { convertToFormValues, KEY_PROVIDER_TYPE } from "../../../util";
import { useAlerts } from "../../../components/alert/Alerts";
type JavaKeystoreFormProps = {
@ -63,7 +63,7 @@ export const JavaKeystoreForm = ({
...component,
parentId: component.parentId,
providerId: providerType,
providerType: "org.keycloak.keys.KeyProvider",
providerType: KEY_PROVIDER_TYPE,
}
);
addAlert(t("saveProviderSuccess"), AlertVariant.success);
@ -72,7 +72,8 @@ export const JavaKeystoreForm = ({
...component,
parentId: component.parentId,
providerId: providerType,
providerType: "org.keycloak.keys.KeyProvider",
providerType: KEY_PROVIDER_TYPE,
config: { priority: ["0"] },
});
handleModalToggle?.();
addAlert(t("saveProviderSuccess"), AlertVariant.success);
@ -129,7 +130,7 @@ export const JavaKeystoreForm = ({
);
const allComponentTypes =
serverInfo.componentTypes?.["org.keycloak.keys.KeyProvider"] ?? [];
serverInfo.componentTypes?.[KEY_PROVIDER_TYPE] ?? [];
const javaKeystoreAlgorithmOptions =
allComponentTypes[3].properties[3].options;

View file

@ -22,7 +22,7 @@ import { useAdminClient, useFetch } from "../../../context/auth/AdminClient";
import { useParams, useRouteMatch } from "react-router-dom";
import { FormAccess } from "../../../components/form-access/FormAccess";
import { ViewHeader } from "../../../components/view-header/ViewHeader";
import { convertToFormValues } from "../../../util";
import { convertToFormValues, KEY_PROVIDER_TYPE } from "../../../util";
import { useAlerts } from "../../../components/alert/Alerts";
type RSAGeneratedFormProps = {
@ -65,7 +65,7 @@ export const RSAGeneratedForm = ({
...component,
parentId: component.parentId,
providerId: providerType,
providerType: "org.keycloak.keys.KeyProvider",
providerType: KEY_PROVIDER_TYPE,
}
);
addAlert(t("saveProviderSuccess"), AlertVariant.success);
@ -74,7 +74,8 @@ export const RSAGeneratedForm = ({
...component,
parentId: component.parentId,
providerId: providerType,
providerType: "org.keycloak.keys.KeyProvider",
providerType: KEY_PROVIDER_TYPE,
config: { priority: ["0"] },
});
handleModalToggle?.();
addAlert(t("saveProviderSuccess"), AlertVariant.success);
@ -121,7 +122,7 @@ export const RSAGeneratedForm = ({
);
const allComponentTypes =
serverInfo.componentTypes?.["org.keycloak.keys.KeyProvider"] ?? [];
serverInfo.componentTypes?.[KEY_PROVIDER_TYPE] ?? [];
const rsaGeneratedKeySizeOptions =
allComponentTypes[5].properties[4].options!;

View file

@ -23,7 +23,7 @@ import { useAdminClient, useFetch } from "../../../context/auth/AdminClient";
import { useParams, useRouteMatch } from "react-router-dom";
import { FormAccess } from "../../../components/form-access/FormAccess";
import { ViewHeader } from "../../../components/view-header/ViewHeader";
import { convertToFormValues } from "../../../util";
import { convertToFormValues, KEY_PROVIDER_TYPE } from "../../../util";
import { useAlerts } from "../../../components/alert/Alerts";
type RSAFormProps = {
@ -70,7 +70,7 @@ export const RSAForm = ({
...component,
parentId: component.parentId,
providerId: providerType,
providerType: "org.keycloak.keys.KeyProvider",
providerType: KEY_PROVIDER_TYPE,
}
);
addAlert(t("saveProviderSuccess"), AlertVariant.success);
@ -79,7 +79,8 @@ export const RSAForm = ({
...component,
parentId: component.parentId,
providerId: providerType,
providerType: "org.keycloak.keys.KeyProvider",
providerType: KEY_PROVIDER_TYPE,
config: { priority: ["0"] },
});
handleModalToggle?.();
addAlert(t("saveProviderSuccess"), AlertVariant.success);
@ -133,7 +134,7 @@ export const RSAForm = ({
);
const allComponentTypes =
serverInfo.componentTypes?.["org.keycloak.keys.KeyProvider"] ?? [];
serverInfo.componentTypes?.[KEY_PROVIDER_TYPE] ?? [];
const rsaAlgOptions = allComponentTypes[4].properties[3].options;

View file

@ -4,6 +4,7 @@ export default {
partialExport: "Partial export",
deleteRealm: "Delete realm",
deleteConfirmTitle: "Delete realm?",
dragInstruction: "Click and drag to change priority",
deleteConfirm:
"If you delete this realm, all associated data will be removed.",
deleteProviderTitle: "Delete key provider?",
@ -19,6 +20,8 @@ export default {
editProvider: "Edit provider",
saveSuccess: "Realm successfully updated",
saveProviderSuccess: "The provider has been saved successfully.",
saveProviderListSuccess:
"The priority of the provider has been updated successfully.",
saveProviderError: "Error saving provider: ",
saveError: "Realm could not be updated: {error}",
general: "General",

View file

@ -89,7 +89,6 @@ export const convertFormValuesToObject = (
const newKey = firstInstanceOnly
? key.replace(/-/, ".")
: key.replace(/-/g, ".");
console.log(newKey);
return { [newKey]: obj[key] };
});
return Object.assign({}, ...keyValues);
@ -189,3 +188,5 @@ export const interpolateTimespan = (forHumans: string) => {
});
}
};
export const KEY_PROVIDER_TYPE = "org.keycloak.keys.KeyProvider";