Implement partial realm export (#1441)
* Implement partial realm export * Replace Switches with Checkboxes
This commit is contained in:
parent
75da445971
commit
5e63bac12e
6 changed files with 242 additions and 2 deletions
57
cypress/integration/partial_export_test.spec.ts
Normal file
57
cypress/integration/partial_export_test.spec.ts
Normal 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");
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
130
src/realm-settings/PartialExport.tsx
Normal file
130
src/realm-settings/PartialExport.tsx
Normal 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>
|
||||
);
|
||||
};
|
|
@ -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")}
|
||||
|
|
|
@ -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}}'",
|
||||
},
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue