Implement partial realm export (#1441)

* Implement partial realm export

* Replace Switches with Checkboxes
This commit is contained in:
Jon Koops 2021-10-29 04:50:25 +02:00 committed by GitHub
parent 75da445971
commit 5e63bac12e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 242 additions and 2 deletions

View file

@ -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");
});
});

View file

@ -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;
}

View file

@ -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");
}
}

View file

@ -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 (
<Modal
variant={ModalVariant.small}
title={t("realm-settings:partialExport")}
isOpen={isOpen}
onClose={onClose}
actions={[
<Button
key="export"
data-testid="export-button"
isDisabled={isExporting}
onClick={exportRealm}
>
{t("common:export")}
</Button>,
<Button
key="cancel"
data-testid="cancel-button"
variant={ButtonVariant.link}
onClick={onClose}
>
{t("common:cancel")}
</Button>,
]}
>
<Stack hasGutter>
<StackItem>
<TextContent>
<Text>{t("partial-export:partialExportHeaderText")}</Text>
</TextContent>
</StackItem>
<StackItem>
<Checkbox
id="include-groups-and-roles-check"
label={t("partial-export:includeGroupsAndRoles")}
isChecked={exportGroupsAndRoles}
onChange={setExportGroupsAndRoles}
/>
</StackItem>
<StackItem>
<Checkbox
id="include-clients-check"
label={t("partial-export:includeClients")}
isChecked={exportClients}
onChange={setExportClients}
/>
</StackItem>
{showWarning && (
<StackItem>
<Alert
data-testid="warning-message"
variant="warning"
title={t("partial-export:exportWarningTitle")}
isInline
>
{t("partial-export:exportWarningDescription")}
</Alert>
</StackItem>
)}
</Stack>
</Modal>
);
};

View file

@ -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)}
/>
<PartialExportDialog
isOpen={partialExportOpen}
onClose={() => setPartialExportOpen(false)}
/>
<ViewHeader
titleKey={toUpperCase(realmName)}
divider={false}
@ -116,7 +122,13 @@ const RealmSettingsHeader = ({
>
{t("partialImport")}
</DropdownItem>,
<DropdownItem key="export">{t("partialExport")}</DropdownItem>,
<DropdownItem
key="export"
data-testid="openPartialExportModal"
onClick={() => setPartialExportOpen(true)}
>
{t("partialExport")}
</DropdownItem>,
<DropdownSeparator key="separator" />,
<DropdownItem key="delete" onClick={toggleDeleteDialog}>
{t("common:delete")}

View file

@ -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}}'",
},
};