From 5e63bac12ec2836705dea62e909b6f340faeebcc Mon Sep 17 00:00:00 2001 From: Jon Koops Date: Fri, 29 Oct 2021 04:50:25 +0200 Subject: [PATCH] Implement partial realm export (#1441) * Implement partial realm export * Replace Switches with Checkboxes --- .../integration/partial_export_test.spec.ts | 57 ++++++++ .../pages/admin_console/SidebarPage.ts | 7 +- .../realm_settings/PartialExportModal.ts | 25 ++++ src/realm-settings/PartialExport.tsx | 130 ++++++++++++++++++ src/realm-settings/RealmSettingsTabs.tsx | 14 +- src/realm-settings/messages.ts | 11 ++ 6 files changed, 242 insertions(+), 2 deletions(-) create mode 100644 cypress/integration/partial_export_test.spec.ts create mode 100644 cypress/support/pages/admin_console/configure/realm_settings/PartialExportModal.ts create mode 100644 src/realm-settings/PartialExport.tsx diff --git a/cypress/integration/partial_export_test.spec.ts b/cypress/integration/partial_export_test.spec.ts new file mode 100644 index 0000000000..851e6a1826 --- /dev/null +++ b/cypress/integration/partial_export_test.spec.ts @@ -0,0 +1,57 @@ +import PartialExportModal from "../support/pages/admin_console/configure/realm_settings/PartialExportModal"; +import RealmSettings from "../support/pages/admin_console/configure/realm_settings/RealmSettings"; +import SidebarPage from "../support/pages/admin_console/SidebarPage"; +import LoginPage from "../support/pages/LoginPage"; +import AdminClient from "../support/util/AdminClient"; +import { keycloakBefore } from "../support/util/keycloak_before"; + +describe("Partial realm export", () => { + const REALM_NAME = "partial-export-test-realm"; + const client = new AdminClient(); + + before(() => client.createRealm(REALM_NAME)); + after(() => client.deleteRealm(REALM_NAME)); + + const loginPage = new LoginPage(); + const sidebarPage = new SidebarPage(); + const modal = new PartialExportModal(); + const realmSettings = new RealmSettings(); + + beforeEach(() => { + keycloakBefore(); + loginPage.logIn(); + sidebarPage.goToRealm(REALM_NAME); + sidebarPage.goToRealmSettings(); + realmSettings.clickActionMenu(); + modal.open(); + }); + + it("closes the dialog", () => { + modal.cancelButton().click(); + modal.exportButton().should("not.exist"); + }); + + it("shows a warning message", () => { + modal.warningMessage().should("not.exist"); + + modal.includeGroupsAndRolesSwitch().click({ force: true }); + modal.warningMessage().should("exist"); + modal.includeGroupsAndRolesSwitch().click({ force: true }); + modal.warningMessage().should("not.exist"); + + modal.includeClientsSwitch().click({ force: true }); + modal.warningMessage().should("exist"); + modal.includeClientsSwitch().click({ force: true }); + modal.warningMessage().should("not.exist"); + }); + + it("exports the realm", () => { + modal.includeGroupsAndRolesSwitch().click({ force: true }); + modal.includeGroupsAndRolesSwitch().click({ force: true }); + modal.exportButton().click(); + cy.readFile( + Cypress.config("downloadsFolder") + "/realm-export.json" + ).should("exist"); + modal.exportButton().should("not.exist"); + }); +}); diff --git a/cypress/support/pages/admin_console/SidebarPage.ts b/cypress/support/pages/admin_console/SidebarPage.ts index 32de077f7d..e9a70f1e65 100644 --- a/cypress/support/pages/admin_console/SidebarPage.ts +++ b/cypress/support/pages/admin_console/SidebarPage.ts @@ -1,3 +1,5 @@ +import { capitalize } from "lodash"; + export default class SidebarPage { private realmsDrpDwn = "realmSelectorToggle"; private realmsList = "realmSelector"; @@ -22,7 +24,10 @@ export default class SidebarPage { goToRealm(realmName: string) { cy.findByTestId(this.realmsList).scrollIntoView().click(); - cy.findByTestId(this.realmsList).get("ul").contains(realmName).click(); + cy.findByTestId(this.realmsList) + .get("ul") + .contains(capitalize(realmName)) + .click(); return this; } diff --git a/cypress/support/pages/admin_console/configure/realm_settings/PartialExportModal.ts b/cypress/support/pages/admin_console/configure/realm_settings/PartialExportModal.ts new file mode 100644 index 0000000000..01146aa525 --- /dev/null +++ b/cypress/support/pages/admin_console/configure/realm_settings/PartialExportModal.ts @@ -0,0 +1,25 @@ +export default class PartialExportModal { + open() { + cy.findByTestId("openPartialExportModal").click(); + } + + exportButton() { + return cy.findByTestId("export-button"); + } + + cancelButton() { + return cy.findByTestId("cancel-button"); + } + + includeGroupsAndRolesSwitch() { + return cy.get("#include-groups-and-roles-check"); + } + + includeClientsSwitch() { + return cy.get("#include-clients-check"); + } + + warningMessage() { + return cy.findByTestId("warning-message"); + } +} diff --git a/src/realm-settings/PartialExport.tsx b/src/realm-settings/PartialExport.tsx new file mode 100644 index 0000000000..14abcbde7d --- /dev/null +++ b/src/realm-settings/PartialExport.tsx @@ -0,0 +1,130 @@ +import { + Alert, + AlertVariant, + Button, + ButtonVariant, + Checkbox, + Modal, + ModalVariant, + Stack, + StackItem, + Text, + TextContent, +} from "@patternfly/react-core"; +import FileSaver from "file-saver"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useAlerts } from "../components/alert/Alerts"; +import { useAdminClient } from "../context/auth/AdminClient"; +import { useRealm } from "../context/realm-context/RealmContext"; +import { prettyPrintJSON } from "../util"; + +export type PartialExportDialogProps = { + isOpen: boolean; + onClose: () => void; +}; + +export const PartialExportDialog = ({ + isOpen, + onClose, +}: PartialExportDialogProps) => { + const { t } = useTranslation(); + const { realm } = useRealm(); + const adminClient = useAdminClient(); + const { addAlert, addError } = useAlerts(); + + const [exportGroupsAndRoles, setExportGroupsAndRoles] = useState(false); + const [exportClients, setExportClients] = useState(false); + const [isExporting, setIsExporting] = useState(false); + + const showWarning = exportGroupsAndRoles || exportClients; + + async function exportRealm() { + setIsExporting(true); + + try { + const realmExport = await adminClient.realms.export({ + realm, + exportClients, + exportGroupsAndRoles, + }); + + FileSaver.saveAs( + new Blob([prettyPrintJSON(realmExport)], { + type: "application/json", + }), + "realm-export.json" + ); + + addAlert(t("partial-export:exportSuccess"), AlertVariant.success); + onClose(); + } catch (error) { + addError("partial-export:exportFail", error); + } + + setIsExporting(false); + } + + return ( + + {t("common:export")} + , + , + ]} + > + + + + {t("partial-export:partialExportHeaderText")} + + + + + + + + + {showWarning && ( + + + {t("partial-export:exportWarningDescription")} + + + )} + + + ); +}; diff --git a/src/realm-settings/RealmSettingsTabs.tsx b/src/realm-settings/RealmSettingsTabs.tsx index 4bcb962658..bb0d184d08 100644 --- a/src/realm-settings/RealmSettingsTabs.tsx +++ b/src/realm-settings/RealmSettingsTabs.tsx @@ -43,6 +43,7 @@ import { RealmSettingsTokensTab } from "./TokensTab"; import { ProfilesTab } from "./ProfilesTab"; import { PoliciesTab } from "./PoliciesTab"; import { PartialImportDialog } from "./PartialImport"; +import { PartialExportDialog } from "./PartialExport"; import { toRealmSettings } from "./routes/RealmSettings"; import { LocalizationTab } from "./LocalizationTab"; import { HelpItem } from "../components/help-enabler/HelpItem"; @@ -67,6 +68,7 @@ const RealmSettingsHeader = ({ const { addAlert, addError } = useAlerts(); const history = useHistory(); const [partialImportOpen, setPartialImportOpen] = useState(false); + const [partialExportOpen, setPartialExportOpen] = useState(false); const [toggleDisableDialog, DisableConfirm] = useConfirmDialog({ titleKey: "realm-settings:disableConfirmTitle", @@ -103,6 +105,10 @@ const RealmSettingsHeader = ({ open={partialImportOpen} toggleDialog={() => setPartialImportOpen(!partialImportOpen)} /> + setPartialExportOpen(false)} + /> {t("partialImport")} , - {t("partialExport")}, + setPartialExportOpen(true)} + > + {t("partialExport")} + , , {t("common:delete")} diff --git a/src/realm-settings/messages.ts b/src/realm-settings/messages.ts index ef24b7846f..76d56ebcbd 100644 --- a/src/realm-settings/messages.ts +++ b/src/realm-settings/messages.ts @@ -724,4 +724,15 @@ export default { SKIP: "Skip", OVERWRITE: "Overwrite", }, + "partial-export": { + partialExportHeaderText: + "Partial export allows you to export realm configuration, and other associated resources into a json file.", + includeGroupsAndRoles: "Include groups and roles", + includeClients: "Include clients", + exportWarningTitle: "Export with caution", + exportWarningDescription: + "If there is a great number of groups, roles or clients in your realm, the operation may make server unresponsive for a while.", + exportSuccess: "Realm successfully exported.", + exportFail: "Could not export realm: '{{error}}'", + }, };