From 9af18e11e28775af8755951e3f2dc78a2ec25f21 Mon Sep 17 00:00:00 2001 From: agagancarczyk Date: Mon, 4 Oct 2021 14:39:54 +0100 Subject: [PATCH] Client add executors (#1284) * client-add-executors: wip * client-add-executors: added logic for deleting client from dropdown * client-add-executors: wip * client-add-executors: added edit profiles * client-add-executors: added refesh client profiles * client-add-executors: added cypress tests * commented out test failing only in CI * Update cypress/integration/realm_settings_test.spec.ts Co-authored-by: Erik Jan de Wit * changed to arrow functions * feedback fixes * feedback fixes * uncommented failing test to see if still failing and why * test possible fix * test fix * test fix * test fix * client-add-executors: reused normaliseProfile func for delete dialog Co-authored-by: Agnieszka Gancarczyk Co-authored-by: Erik Jan de Wit --- .../integration/realm_settings_test.spec.ts | 32 +++++ .../realm_settings/RealmSettingsPage.ts | 65 +++++++++- src/realm-settings/NewClientProfileForm.tsx | 121 +++++++++++++----- src/realm-settings/ProfilesTab.tsx | 88 ++++++++++--- src/realm-settings/RealmSettingsSection.css | 10 +- src/realm-settings/messages.ts | 5 + 6 files changed, 267 insertions(+), 54 deletions(-) diff --git a/cypress/integration/realm_settings_test.spec.ts b/cypress/integration/realm_settings_test.spec.ts index 1fda1fb593..885e1696e0 100644 --- a/cypress/integration/realm_settings_test.spec.ts +++ b/cypress/integration/realm_settings_test.spec.ts @@ -469,5 +469,37 @@ describe("Realm settings tests", () => { it("Check deleting the client profile", () => { realmSettingsPage.shouldDeleteClientProfileDialog(); }); + + it("Check navigating between Form View and JSON editor", () => { + realmSettingsPage.shouldNavigateBetweenFormAndJSONView(); + }); + + it("Check saving changed JSON profiles", () => { + realmSettingsPage.shouldSaveChangedJSONProfiles(); + realmSettingsPage.shouldDeleteClientProfileDialog(); + }); + + it("Should not create duplicate client profile", () => { + realmSettingsPage.shouldCompleteAndCreateNewClientProfile(); + + sidebarPage.goToRealmSettings(); + cy.findByTestId("rs-clientPolicies-tab").click(); + cy.findByTestId("rs-profiles-clientPolicies-tab").click(); + realmSettingsPage.shouldCompleteAndCreateNewClientProfile(); + realmSettingsPage.shouldNotCreateDuplicateClientProfile(); + + sidebarPage.goToRealmSettings(); + cy.findByTestId("rs-clientPolicies-tab").click(); + cy.findByTestId("rs-profiles-clientPolicies-tab").click(); + realmSettingsPage.shouldDeleteClientProfileDialog(); + }); + + it("Check deleting newly created client profile from create view via dropdown", () => { + realmSettingsPage.shouldRemoveClientFromCreateView(); + }); + + it("Check reloading JSON profiles", () => { + realmSettingsPage.shouldReloadJSONProfiles(); + }); }); }); diff --git a/cypress/support/pages/admin_console/manage/realm_settings/RealmSettingsPage.ts b/cypress/support/pages/admin_console/manage/realm_settings/RealmSettingsPage.ts index 481234a661..381d2ce94c 100644 --- a/cypress/support/pages/admin_console/manage/realm_settings/RealmSettingsPage.ts +++ b/cypress/support/pages/admin_console/manage/realm_settings/RealmSettingsPage.ts @@ -143,6 +143,8 @@ export default class RealmSettingsPage { executeActionsSelectMenu = "#kc-execute-actions-select-menu"; executeActionsSelectMenuList = "#kc-execute-actions-select-menu > div > ul"; + private formViewProfilesView = "formView-profilesView"; + private jsonEditorProfilesView = "jsonEditor-profilesView"; private createProfileBtn = "createProfile"; private formViewSelect = "formView-profilesView"; private jsonEditorSelect = "jsonEditor-profilesView"; @@ -156,6 +158,10 @@ export default class RealmSettingsPage { private deleteDialogTitle = ".pf-c-modal-box__title-text"; private deleteDialogBodyText = ".pf-c-modal-box__body"; private deleteDialogCancelBtn = ".pf-c-button.pf-m-link"; + private jsonEditorSaveBtn = "jsonEditor-saveBtn"; + private jsonEditorReloadBtn = "jsonEditor-reloadBtn"; + private jsonEditor = ".monaco-scrollable-element.editor-scrollable.vs"; + private createClientDrpDwn = ".pf-c-dropdown.pf-m-align-right"; selectLoginThemeType(themeType: string) { cy.get(this.selectLoginTheme).click(); @@ -519,7 +525,64 @@ export default class RealmSettingsPage { cy.get(this.moreDrpDwn).last().click(); cy.get(this.moreDrpDwnItems).click(); cy.findByTestId("modalConfirm").contains("Delete").click(); - cy.get("table").should("not.have.text", "Test"); cy.get(this.alertMessage).should("be.visible", "Client profile deleted"); + cy.get("table").should("not.have.text", "Test"); + } + + shouldNavigateBetweenFormAndJSONView() { + cy.findByTestId(this.jsonEditorProfilesView).check(); + cy.findByTestId(this.jsonEditorSaveBtn).contains("Save"); + cy.findByTestId(this.jsonEditorReloadBtn).contains("Reload"); + cy.findByTestId(this.formViewProfilesView).check(); + cy.findByTestId(this.createProfileBtn).contains("Create client profile"); + } + + shouldSaveChangedJSONProfiles() { + cy.findByTestId(this.jsonEditorProfilesView).check(); + cy.get(this.jsonEditor).type(`{pageup}{del} [{ + "name": "Test", + "description": "Test Description", + "executors": [], + "global": false + }, {downarrow}{end}{backspace}{backspace}`); + cy.findByTestId(this.jsonEditorSaveBtn).click(); + cy.get(this.alertMessage).should( + "be.visible", + "The client profiles configuration was updated" + ); + cy.findByTestId(this.formViewProfilesView).check(); + cy.get("table").should("be.visible").contains("td", "Test"); + } + + shouldNotCreateDuplicateClientProfile() { + cy.get(this.alertMessage).should( + "be.visible", + "Could not create client profile: 'proposed client profile name duplicated.'" + ); + } + + shouldRemoveClientFromCreateView() { + cy.findByTestId(this.createProfileBtn).click(); + cy.findByTestId(this.newClientProfileNameInput).type("Test again"); + cy.findByTestId(this.newClientProfileDescriptionInput).type( + "Test Again Description" + ); + cy.findByTestId(this.saveNewClientProfileBtn).click(); + cy.get(this.alertMessage).should( + "be.visible", + "New client profile created" + ); + cy.get(this.createClientDrpDwn).contains("Action").click(); + cy.findByTestId("deleteClientProfileDropdown").click(); + cy.findByTestId("modalConfirm").contains("Delete").click(); + cy.get(this.alertMessage).should("be.visible", "Client profile deleted"); + cy.get("table").should("not.have.text", "Test Again Description"); + } + + shouldReloadJSONProfiles() { + cy.findByTestId(this.jsonEditorProfilesView).check(); + cy.findByTestId(this.jsonEditorReloadBtn).contains("Reload").click(); + cy.findByTestId(this.jsonEditorSaveBtn).contains("Save"); + cy.findByTestId(this.jsonEditorReloadBtn).contains("Reload"); } } diff --git a/src/realm-settings/NewClientProfileForm.tsx b/src/realm-settings/NewClientProfileForm.tsx index 1ca6cc4fc1..0844c7834e 100644 --- a/src/realm-settings/NewClientProfileForm.tsx +++ b/src/realm-settings/NewClientProfileForm.tsx @@ -3,7 +3,11 @@ import { ActionGroup, AlertVariant, Button, + ButtonVariant, Divider, + DropdownItem, + Flex, + FlexItem, FormGroup, PageSection, Text, @@ -16,7 +20,7 @@ import { useTranslation } from "react-i18next"; import { useForm } from "react-hook-form"; import { FormAccess } from "../components/form-access/FormAccess"; import { ViewHeader } from "../components/view-header/ViewHeader"; -import { Link } from "react-router-dom"; +import { Link, useHistory } from "react-router-dom"; import { useRealm } from "../context/realm-context/RealmContext"; import { useAlerts } from "../components/alert/Alerts"; import { useAdminClient, useFetch } from "../context/auth/AdminClient"; @@ -24,13 +28,14 @@ import type ClientProfileRepresentation from "@keycloak/keycloak-admin-client/li import { HelpItem } from "../components/help-enabler/HelpItem"; import { PlusCircleIcon } from "@patternfly/react-icons"; import "./RealmSettingsSection.css"; +import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; type NewClientProfileForm = Required; const defaultValues: NewClientProfileForm = { name: "", - executors: [], description: "", + executors: [], }; export const NewClientProfileForm = () => { @@ -46,6 +51,10 @@ export const NewClientProfileForm = () => { >([]); const [profiles, setProfiles] = useState([]); const [showAddExecutorsForm, setShowAddExecutorsForm] = useState(false); + const [createdProfile, setCreatedProfile] = + useState(); + const form = getValues(); + const history = useHistory(); useFetch( () => @@ -77,14 +86,56 @@ export const NewClientProfileForm = () => { AlertVariant.success ); setShowAddExecutorsForm(true); + setCreatedProfile(createdProfile); } catch (error) { addError("realm-settings:createClientProfileError", error); } }; + const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ + titleKey: t("deleteClientProfileConfirmTitle"), + messageKey: t("deleteClientProfileConfirm"), + continueButtonLabel: t("delete"), + continueButtonVariant: ButtonVariant.danger, + onConfirm: async () => { + const updatedProfiles = profiles.filter( + (profile) => profile.name !== createdProfile?.name + ); + + try { + await adminClient.clientPolicies.createProfiles({ + profiles: updatedProfiles, + globalProfiles, + }); + addAlert(t("deleteClientSuccess"), AlertVariant.success); + history.push(`/${realm}/realm-settings/clientPolicies`); + } catch (error) { + addError(t("deleteClientError"), error); + } + }, + }); + return ( <> - + + + {t("deleteClientProfile")} + , + ] + : undefined + } + /> { variant="primary" onClick={save} data-testid="saveCreateProfile" + isDisabled={showAddExecutorsForm ? true : false} > {t("common:save")} @@ -133,41 +185,44 @@ export const NewClientProfileForm = () => { )} data-testid="cancelCreateProfile" > - {t("common:cancel")} + {showAddExecutorsForm + ? t("realm-settings:reload") + : t("common:cancel")} {showAddExecutorsForm && ( <> - - } - > - - + + + + {t("executors")} + + + + + + + - + {t("realm-settings:emptyExecutors")} diff --git a/src/realm-settings/ProfilesTab.tsx b/src/realm-settings/ProfilesTab.tsx index 244f633d82..a2ca5ce45b 100644 --- a/src/realm-settings/ProfilesTab.tsx +++ b/src/realm-settings/ProfilesTab.tsx @@ -1,9 +1,11 @@ -import React, { useMemo, useState } from "react"; +import React, { useState } from "react"; import { omit } from "lodash"; import { + ActionGroup, AlertVariant, Button, ButtonVariant, + FormGroup, Label, PageSection, Spinner, @@ -38,6 +40,7 @@ export const ProfilesTab = () => { useState(); const [selectedProfile, setSelectedProfile] = useState(); const [show, setShow] = useState(false); + const [code, setCode] = useState(); const [key, setKey] = useState(0); useFetch( @@ -62,16 +65,16 @@ export const ProfilesTab = () => { const allClientProfiles = globalProfiles?.concat(profiles ?? []); setTableProfiles(allClientProfiles || []); + setCode(JSON.stringify(allClientProfiles, null, 2)); }, [key] ); const loader = async () => tableProfiles ?? []; - const code = useMemo( - () => JSON.stringify(tableProfiles, null, 2), - [tableProfiles] - ); + const normalizeProfile = ( + profile: ClientProfile + ): ClientProfileRepresentation => omit(profile, "global"); const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ titleKey: t("deleteClientProfileConfirmTitle"), @@ -83,7 +86,9 @@ export const ProfilesTab = () => { ?.filter( (profile) => profile.name !== selectedProfile?.name && !profile.global ) - .map((profile) => omit(profile, "global")); + .map((profile) => + normalizeProfile(profile) + ); try { await adminClient.clientPolicies.createProfiles({ @@ -112,6 +117,39 @@ export const ProfilesTab = () => { ); } + const save = async () => { + if (!code) { + return; + } + + try { + const obj: ClientProfile[] = JSON.parse(code); + const changedProfiles = obj + .filter((profile) => !profile.global) + .map((profile) => normalizeProfile(profile)); + + const changedGlobalProfiles = obj + .filter((profile) => profile.global) + .map((profile) => normalizeProfile(profile)); + + try { + await adminClient.clientPolicies.createProfiles({ + profiles: changedProfiles, + globalProfiles: changedGlobalProfiles, + }); + addAlert( + t("realm-settings:updateClientProfilesSuccess"), + AlertVariant.success + ); + setKey(key + 1); + } catch (error) { + addError("realm-settings:updateClientProfilesError", error); + } + } catch (error) { + console.warn("Invalid json, ignoring value using {}"); + } + }; + return ( <> @@ -195,26 +233,42 @@ export const ProfilesTab = () => { } /> ) : ( - <> +
{ + setCode(value ?? ""); + }} />
-
- - -
- + +
+ + +
+
+
)} ); diff --git a/src/realm-settings/RealmSettingsSection.css b/src/realm-settings/RealmSettingsSection.css index 5eda9c3b3b..ac4bac4068 100644 --- a/src/realm-settings/RealmSettingsSection.css +++ b/src/realm-settings/RealmSettingsSection.css @@ -194,6 +194,10 @@ article.pf-c-card.pf-m-flat.kc-login-settings-template transform: scale(1.6); } -.kc-addExecutor { - float: right; -} \ No newline at end of file +.kc-emptyExecutors { + color: #8D9195; +} + +.kc-action-dropdown { + background-color: transparent; +} diff --git a/src/realm-settings/messages.ts b/src/realm-settings/messages.ts index b6e51fd064..1d021f4e75 100644 --- a/src/realm-settings/messages.ts +++ b/src/realm-settings/messages.ts @@ -234,6 +234,7 @@ export default { deleteClientSuccess: "Client profile deleted", deleteClientError: "Could not delete profile: {{error}}", createClientProfile: "Create client profile", + deleteClientProfile: "Delete this client profile", createClientProfileSuccess: "New client profile created", createClientProfileError: "Could not create client profile: '{{error}}'", createClientProfileNameHelperText: @@ -252,6 +253,10 @@ export default { executorsHelpItem: "Executors help item", addExecutor: "Add executor", emptyExecutors: "No executors configured", + updateClientProfilesSuccess: + "The client profiles configuration was updated", + updateClientProfilesError: + "Provided JSON is incorrect: Unexpected token { in JSON", tokens: "Tokens", key: "Key", value: "Value",