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 {
|
export default class SidebarPage {
|
||||||
private realmsDrpDwn = "realmSelectorToggle";
|
private realmsDrpDwn = "realmSelectorToggle";
|
||||||
private realmsList = "realmSelector";
|
private realmsList = "realmSelector";
|
||||||
|
@ -22,7 +24,10 @@ export default class SidebarPage {
|
||||||
|
|
||||||
goToRealm(realmName: string) {
|
goToRealm(realmName: string) {
|
||||||
cy.findByTestId(this.realmsList).scrollIntoView().click();
|
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;
|
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 { ProfilesTab } from "./ProfilesTab";
|
||||||
import { PoliciesTab } from "./PoliciesTab";
|
import { PoliciesTab } from "./PoliciesTab";
|
||||||
import { PartialImportDialog } from "./PartialImport";
|
import { PartialImportDialog } from "./PartialImport";
|
||||||
|
import { PartialExportDialog } from "./PartialExport";
|
||||||
import { toRealmSettings } from "./routes/RealmSettings";
|
import { toRealmSettings } from "./routes/RealmSettings";
|
||||||
import { LocalizationTab } from "./LocalizationTab";
|
import { LocalizationTab } from "./LocalizationTab";
|
||||||
import { HelpItem } from "../components/help-enabler/HelpItem";
|
import { HelpItem } from "../components/help-enabler/HelpItem";
|
||||||
|
@ -67,6 +68,7 @@ const RealmSettingsHeader = ({
|
||||||
const { addAlert, addError } = useAlerts();
|
const { addAlert, addError } = useAlerts();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const [partialImportOpen, setPartialImportOpen] = useState(false);
|
const [partialImportOpen, setPartialImportOpen] = useState(false);
|
||||||
|
const [partialExportOpen, setPartialExportOpen] = useState(false);
|
||||||
|
|
||||||
const [toggleDisableDialog, DisableConfirm] = useConfirmDialog({
|
const [toggleDisableDialog, DisableConfirm] = useConfirmDialog({
|
||||||
titleKey: "realm-settings:disableConfirmTitle",
|
titleKey: "realm-settings:disableConfirmTitle",
|
||||||
|
@ -103,6 +105,10 @@ const RealmSettingsHeader = ({
|
||||||
open={partialImportOpen}
|
open={partialImportOpen}
|
||||||
toggleDialog={() => setPartialImportOpen(!partialImportOpen)}
|
toggleDialog={() => setPartialImportOpen(!partialImportOpen)}
|
||||||
/>
|
/>
|
||||||
|
<PartialExportDialog
|
||||||
|
isOpen={partialExportOpen}
|
||||||
|
onClose={() => setPartialExportOpen(false)}
|
||||||
|
/>
|
||||||
<ViewHeader
|
<ViewHeader
|
||||||
titleKey={toUpperCase(realmName)}
|
titleKey={toUpperCase(realmName)}
|
||||||
divider={false}
|
divider={false}
|
||||||
|
@ -116,7 +122,13 @@ const RealmSettingsHeader = ({
|
||||||
>
|
>
|
||||||
{t("partialImport")}
|
{t("partialImport")}
|
||||||
</DropdownItem>,
|
</DropdownItem>,
|
||||||
<DropdownItem key="export">{t("partialExport")}</DropdownItem>,
|
<DropdownItem
|
||||||
|
key="export"
|
||||||
|
data-testid="openPartialExportModal"
|
||||||
|
onClick={() => setPartialExportOpen(true)}
|
||||||
|
>
|
||||||
|
{t("partialExport")}
|
||||||
|
</DropdownItem>,
|
||||||
<DropdownSeparator key="separator" />,
|
<DropdownSeparator key="separator" />,
|
||||||
<DropdownItem key="delete" onClick={toggleDeleteDialog}>
|
<DropdownItem key="delete" onClick={toggleDeleteDialog}>
|
||||||
{t("common:delete")}
|
{t("common:delete")}
|
||||||
|
|
|
@ -724,4 +724,15 @@ export default {
|
||||||
SKIP: "Skip",
|
SKIP: "Skip",
|
||||||
OVERWRITE: "Overwrite",
|
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