Re-use User Profile logic from shared context (#1951)

This commit is contained in:
Jon Koops 2022-02-07 12:49:47 +01:00 committed by GitHub
parent 7dfa1a8b19
commit d85ff6e9be
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 121 additions and 111 deletions

View file

@ -1,4 +1,3 @@
import type UserProfileConfig from "@keycloak/keycloak-admin-client/lib/defs/userProfileConfig";
import type { UserProfileGroup } from "@keycloak/keycloak-admin-client/lib/defs/userProfileConfig"; import type { UserProfileGroup } from "@keycloak/keycloak-admin-client/lib/defs/userProfileConfig";
import { import {
Button, Button,
@ -12,17 +11,10 @@ import { Link, useHistory } from "react-router-dom";
import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog"; import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog";
import { ListEmptyState } from "../../components/list-empty-state/ListEmptyState"; import { ListEmptyState } from "../../components/list-empty-state/ListEmptyState";
import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable"; import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable";
import type { OnSaveCallback } from "./UserProfileTab"; import { useUserProfile } from "./UserProfileContext";
type AttributesGroupTabProps = { export const AttributesGroupTab = () => {
config?: UserProfileConfig; const { config, save } = useUserProfile();
onSave: OnSaveCallback;
};
export const AttributesGroupTab = ({
config,
onSave,
}: AttributesGroupTabProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const history = useHistory(); const history = useHistory();
const [key, setKey] = useState(0); const [key, setKey] = useState(0);
@ -50,7 +42,7 @@ export const AttributesGroupTab = ({
(group) => group !== groupToDelete (group) => group !== groupToDelete
); );
onSave( save(
{ ...config, groups }, { ...config, groups },
{ {
successMessageKey: "attributes-group:deleteSuccess", successMessageKey: "attributes-group:deleteSuccess",

View file

@ -1,4 +1,3 @@
import type UserProfileConfig from "@keycloak/keycloak-admin-client/lib/defs/userProfileConfig";
import { CodeEditor, Language } from "@patternfly/react-code-editor"; import { CodeEditor, Language } from "@patternfly/react-code-editor";
import { ActionGroup, Button, Form, PageSection } from "@patternfly/react-core"; import { ActionGroup, Button, Form, PageSection } from "@patternfly/react-core";
import type { editor } from "monaco-editor"; import type { editor } from "monaco-editor";
@ -6,19 +5,10 @@ import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useAlerts } from "../../components/alert/Alerts"; import { useAlerts } from "../../components/alert/Alerts";
import { prettyPrintJSON } from "../../util"; import { prettyPrintJSON } from "../../util";
import type { OnSaveCallback } from "./UserProfileTab"; import { useUserProfile } from "./UserProfileContext";
type JsonEditorTabProps = { export const JsonEditorTab = () => {
config?: UserProfileConfig; const { config, save, isSaving } = useUserProfile();
onSave: OnSaveCallback;
isSaving: boolean;
};
export const JsonEditorTab = ({
config,
onSave,
isSaving,
}: JsonEditorTabProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { addError } = useAlerts(); const { addError } = useAlerts();
const [editor, setEditor] = useState<editor.IStandaloneCodeEditor>(); const [editor, setEditor] = useState<editor.IStandaloneCodeEditor>();
@ -29,7 +19,7 @@ export const JsonEditorTab = ({
editor?.setValue(config ? prettyPrintJSON(config) : ""); editor?.setValue(config ? prettyPrintJSON(config) : "");
} }
function save() { function handleSave() {
const value = editor?.getValue(); const value = editor?.getValue();
if (!value) { if (!value) {
@ -37,7 +27,7 @@ export const JsonEditorTab = ({
} }
try { try {
onSave(JSON.parse(value)); save(JSON.parse(value));
} catch (error) { } catch (error) {
addError("realm-settings:invalidJsonError", error); addError("realm-settings:invalidJsonError", error);
return; return;
@ -54,7 +44,7 @@ export const JsonEditorTab = ({
/> />
<Form> <Form>
<ActionGroup> <ActionGroup>
<Button variant="primary" onClick={save} isDisabled={isSaving}> <Button variant="primary" onClick={handleSave} isDisabled={isSaving}>
{t("common:save")} {t("common:save")}
</Button> </Button>
<Button variant="link" onClick={resetCode} isDisabled={isSaving}> <Button variant="link" onClick={resetCode} isDisabled={isSaving}>

View file

@ -0,0 +1,76 @@
import type UserProfileConfig from "@keycloak/keycloak-admin-client/lib/defs/userProfileConfig";
import { AlertVariant } from "@patternfly/react-core";
import React, { createContext, FunctionComponent, useState } from "react";
import { useTranslation } from "react-i18next";
import { useAlerts } from "../../components/alert/Alerts";
import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
import { useRealm } from "../../context/realm-context/RealmContext";
import useRequiredContext from "../../utils/useRequiredContext";
type UserProfileProps = {
config: UserProfileConfig | null;
save: SaveCallback;
isSaving: boolean;
};
export type SaveCallback = (
updatedConfig: UserProfileConfig,
options?: SaveOptions
) => Promise<void>;
export type SaveOptions = {
successMessageKey?: string;
errorMessageKey?: string;
};
export const UserProfile = createContext<UserProfileProps | undefined>(
undefined
);
export const UserProfileProvider: FunctionComponent = ({ children }) => {
const adminClient = useAdminClient();
const { realm } = useRealm();
const { addAlert, addError } = useAlerts();
const { t } = useTranslation();
const [config, setConfig] = useState<UserProfileConfig | null>(null);
const [refreshCount, setRefreshCount] = useState(0);
const [isSaving, setIsSaving] = useState(false);
useFetch(
() => adminClient.users.getProfile({ realm }),
(config) => setConfig(config),
[refreshCount]
);
const save: SaveCallback = async (updatedConfig, options) => {
setIsSaving(true);
try {
await adminClient.users.updateProfile({
...updatedConfig,
realm,
});
setRefreshCount(refreshCount + 1);
addAlert(
t(options?.successMessageKey ?? "realm-settings:userProfileSuccess"),
AlertVariant.success
);
} catch (error) {
addError(
options?.errorMessageKey ?? "realm-settings:userProfileError",
error
);
}
setIsSaving(false);
};
return (
<UserProfile.Provider value={{ config, save, isSaving }}>
{children}
</UserProfile.Provider>
);
};
export const useUserProfile = () => useRequiredContext(UserProfile);

View file

@ -1,103 +1,55 @@
import type UserProfileConfig from "@keycloak/keycloak-admin-client/lib/defs/userProfileConfig"; import { Tab, TabTitleText } from "@patternfly/react-core";
import { AlertVariant, Tab, TabTitleText } from "@patternfly/react-core"; import React from "react";
import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useHistory } from "react-router-dom"; import { useHistory } from "react-router-dom";
import { useAlerts } from "../../components/alert/Alerts";
import { import {
routableTab, routableTab,
RoutableTabs, RoutableTabs,
} from "../../components/routable-tabs/RoutableTabs"; } from "../../components/routable-tabs/RoutableTabs";
import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
import { useRealm } from "../../context/realm-context/RealmContext"; import { useRealm } from "../../context/realm-context/RealmContext";
import { toUserProfile } from "../routes/UserProfile"; import { toUserProfile } from "../routes/UserProfile";
import { AttributesGroupTab } from "./AttributesGroupTab"; import { AttributesGroupTab } from "./AttributesGroupTab";
import { JsonEditorTab } from "./JsonEditorTab"; import { JsonEditorTab } from "./JsonEditorTab";
import { UserProfileProvider } from "./UserProfileContext";
export type OnSaveCallback = (
updatedProfiles: UserProfileConfig,
options?: OnSaveOptions
) => Promise<void>;
export type OnSaveOptions = {
successMessageKey?: string;
errorMessageKey?: string;
};
export const UserProfileTab = () => { export const UserProfileTab = () => {
const adminClient = useAdminClient();
const { realm } = useRealm(); const { realm } = useRealm();
const { t } = useTranslation("realm-settings"); const { t } = useTranslation("realm-settings");
const history = useHistory(); const history = useHistory();
const { addAlert, addError } = useAlerts();
const [config, setConfig] = useState<UserProfileConfig>();
const [isSaving, setIsSaving] = useState(false);
const [refreshCount, setRefreshCount] = useState(0);
useFetch(
() => adminClient.users.getProfile({ realm }),
(config) => setConfig(config),
[refreshCount]
);
const onSave: OnSaveCallback = async (
updatedProfiles: UserProfileConfig,
options?: OnSaveOptions
) => {
setIsSaving(true);
try {
await adminClient.users.updateProfile({
...updatedProfiles,
realm,
});
setRefreshCount(refreshCount + 1);
addAlert(
t(options?.successMessageKey ?? "userProfileSuccess"),
AlertVariant.success
);
} catch (error) {
addError(
options?.errorMessageKey ?? "realm-settings:userProfileError",
error
);
}
setIsSaving(false);
};
return ( return (
<RoutableTabs <UserProfileProvider>
defaultLocation={toUserProfile({ realm, tab: "attributes" })} <RoutableTabs
mountOnEnter defaultLocation={toUserProfile({ realm, tab: "attributes" })}
> mountOnEnter
<Tab
title={<TabTitleText>{t("attributes")}</TabTitleText>}
{...routableTab({
to: toUserProfile({ realm, tab: "attributes" }),
history,
})}
></Tab>
<Tab
title={<TabTitleText>{t("attributesGroup")}</TabTitleText>}
data-testid="attributesGroupTab"
{...routableTab({
to: toUserProfile({ realm, tab: "attributesGroup" }),
history,
})}
> >
<AttributesGroupTab config={config} onSave={onSave} /> <Tab
</Tab> title={<TabTitleText>{t("attributes")}</TabTitleText>}
<Tab {...routableTab({
title={<TabTitleText>{t("jsonEditor")}</TabTitleText>} to: toUserProfile({ realm, tab: "attributes" }),
{...routableTab({ history,
to: toUserProfile({ realm, tab: "jsonEditor" }), })}
history, ></Tab>
})} <Tab
> title={<TabTitleText>{t("attributesGroup")}</TabTitleText>}
<JsonEditorTab config={config} onSave={onSave} isSaving={isSaving} /> data-testid="attributesGroupTab"
</Tab> {...routableTab({
</RoutableTabs> to: toUserProfile({ realm, tab: "attributesGroup" }),
history,
})}
>
<AttributesGroupTab />
</Tab>
<Tab
title={<TabTitleText>{t("jsonEditor")}</TabTitleText>}
{...routableTab({
to: toUserProfile({ realm, tab: "jsonEditor" }),
history,
})}
>
<JsonEditorTab />
</Tab>
</RoutableTabs>
</UserProfileProvider>
); );
}; };