added missing action menu on flow details (#1311)

This commit is contained in:
Erik Jan de Wit 2021-10-14 16:54:07 +02:00 committed by GitHub
parent 7ba966b517
commit 0b4cb21134
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 217 additions and 13 deletions

View file

@ -5,7 +5,7 @@ export default class DuplicateFlowModal {
fill(name?: string, description?: string) {
if (name) {
cy.findByTestId(this.aliasInput).type(name);
cy.findByTestId(this.aliasInput).clear().type(name);
if (description) cy.get(this.descriptionInput).type(description);
}

View file

@ -219,7 +219,7 @@ export const AuthenticationSection = () => {
key={key}
loader={loader}
ariaLabelKey="authentication:title"
searchPlaceholderKey="authentication:searchForEvent"
searchPlaceholderKey="authentication:searchForFlow"
toolbarItem={
<ToolbarItem>
<Button

View file

@ -37,7 +37,7 @@ export const DuplicateFlowModal = ({
useEffect(() => {
setValue("description", description);
setValue("name", t("copyOf", { name }));
setValue("alias", t("copyOf", { name }));
}, [name, description, setValue]);
const save = async () => {

View file

@ -0,0 +1,89 @@
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { FormProvider, useForm } from "react-hook-form";
import {
AlertVariant,
Button,
ButtonVariant,
Form,
Modal,
ModalVariant,
} from "@patternfly/react-core";
import type AuthenticationFlowRepresentation from "@keycloak/keycloak-admin-client/lib/defs/authenticationFlowRepresentation";
import { useAdminClient } from "../context/auth/AdminClient";
import { useAlerts } from "../components/alert/Alerts";
import { NameDescription } from "./form/NameDescription";
type EditFlowModalProps = {
flow: AuthenticationFlowRepresentation;
toggleDialog: () => void;
};
type EditForm = {
alias: string;
description: string;
};
export const EditFlowModal = ({ flow, toggleDialog }: EditFlowModalProps) => {
const { t } = useTranslation("authentication");
const form = useForm<EditForm>({
shouldUnregister: false,
});
const { reset, handleSubmit } = form;
const adminClient = useAdminClient();
const { addAlert, addError } = useAlerts();
useEffect(() => {
reset(flow);
}, [flow, reset]);
const save = async (values: EditForm) => {
try {
await adminClient.authenticationManagement.updateFlow(
{ flowId: flow.id! },
{ ...flow, ...values }
);
addAlert(t("updateFlowSuccess"), AlertVariant.success);
} catch (error) {
addError("authentication:updateFlowError", error);
}
toggleDialog();
};
return (
<Modal
title={t("editFlow")}
isOpen={true}
onClose={toggleDialog}
variant={ModalVariant.small}
actions={[
<Button
id="modal-confirm"
key="confirm"
onClick={handleSubmit(save)}
data-testid="confirm"
>
{t("edit")}
</Button>,
<Button
data-testid="cancel"
id="modal-cancel"
key="cancel"
variant={ButtonVariant.link}
onClick={() => {
toggleDialog();
}}
>
{t("common:cancel")}
</Button>,
]}
>
<FormProvider {...form}>
<Form isHorizontal>
<NameDescription />
</Form>
</FormProvider>
</Modal>
);
};

View file

@ -1,5 +1,5 @@
import React, { useState } from "react";
import { useParams } from "react-router-dom";
import { useHistory, useParams } from "react-router-dom";
import { Trans, useTranslation } from "react-i18next";
import {
DataList,
@ -13,13 +13,14 @@ import {
ActionGroup,
Button,
ButtonVariant,
DropdownItem,
} from "@patternfly/react-core";
import { CheckCircleIcon, PlusIcon, TableIcon } from "@patternfly/react-icons";
import type AuthenticationExecutionInfoRepresentation from "@keycloak/keycloak-admin-client/lib/defs/authenticationExecutionInfoRepresentation";
import type { AuthenticationProviderRepresentation } from "@keycloak/keycloak-admin-client/lib/defs/authenticatorConfigRepresentation";
import type AuthenticationFlowRepresentation from "@keycloak/keycloak-admin-client/lib/defs/authenticationFlowRepresentation";
import type { FlowParams } from "./routes/Flow";
import { FlowParams, toFlow } from "./routes/Flow";
import { ViewHeader } from "../components/view-header/ViewHeader";
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
import { EmptyExecutionState } from "./EmptyExecutionState";
@ -37,6 +38,10 @@ import { useAlerts } from "../components/alert/Alerts";
import { AddStepModal } from "./components/modals/AddStepModal";
import { AddSubFlowModal, Flow } from "./components/modals/AddSubFlowModal";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import { DuplicateFlowModal } from "./DuplicateFlowModal";
import { useRealm } from "../context/realm-context/RealmContext";
import { toAuthentication } from "./routes/Authentication";
import { EditFlowModal } from "./EditFlowModal";
export const providerConditionFilter = (
value: AuthenticationProviderRepresentation
@ -45,8 +50,10 @@ export const providerConditionFilter = (
export const FlowDetails = () => {
const { t } = useTranslation("authentication");
const adminClient = useAdminClient();
const { realm } = useRealm();
const { addAlert, addError } = useAlerts();
const { id, usedBy, builtIn } = useParams<FlowParams>();
const history = useHistory();
const [key, setKey] = useState(0);
const refresh = () => setKey(new Date().getTime());
@ -62,6 +69,8 @@ export const FlowDetails = () => {
const [showAddSubFlowDialog, setShowSubFlowDialog] = useState<boolean>();
const [selectedExecution, setSelectedExecution] =
useState<ExpandableExecution>();
const [open, setOpen] = useState(false);
const [edit, setEdit] = useState(false);
useFetch(
async () => {
@ -168,6 +177,20 @@ export const FlowDetails = () => {
}
};
const setAsDefault = async () => {
try {
const r = await adminClient.realms.findOne({ realm });
await adminClient.realms.update(
{ realm },
{ ...r, browserFlow: flow?.alias }
);
addAlert(t("updateFlowSuccess"), AlertVariant.success);
history.push(toFlow({ id, realm, usedBy: "default", builtIn }));
} catch (error) {
addError("authentication:updateFlowError", error);
}
};
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
titleKey: "authentication:deleteConfirmExecution",
children: (
@ -191,10 +214,90 @@ export const FlowDetails = () => {
},
});
const [toggleDeleteFlow, DeleteFlowConfirm] = useConfirmDialog({
titleKey: "authentication:deleteConfirmFlow",
children: (
<Trans i18nKey="authentication:deleteConfirmFlowMessage">
{" "}
<strong>{{ flow: flow?.alias || "" }}</strong>.
</Trans>
),
continueButtonLabel: "common:delete",
continueButtonVariant: ButtonVariant.danger,
onConfirm: async () => {
try {
await adminClient.authenticationManagement.deleteFlow({
flowId: flow!.id!,
});
history.push(toAuthentication({ realm }));
addAlert(t("deleteFlowSuccess"), AlertVariant.success);
} catch (error) {
addError("authentication:deleteFlowError", error);
}
},
});
const hasExecutions = executionList?.expandableList.length !== 0;
const dropdownItems = [
...(usedBy !== "default"
? [
<DropdownItem
data-testid="set-as-default"
key="default"
onClick={() => setAsDefault()}
>
{t("setAsDefault")}
</DropdownItem>,
]
: []),
<DropdownItem key="duplicate" onClick={() => setOpen(true)}>
{t("duplicate")}
</DropdownItem>,
...(!builtIn
? [
<DropdownItem
data-testid="edit-flow"
key="edit"
onClick={() => setEdit(true)}
>
{t("editInfo")}
</DropdownItem>,
<DropdownItem
data-testid="delete-flow"
key="delete"
onClick={() => toggleDeleteFlow()}
>
{t("common:delete")}
</DropdownItem>,
]
: []),
];
return (
<>
{open && (
<DuplicateFlowModal
name={flow?.alias!}
description={flow?.description!}
toggleDialog={() => setOpen(!open)}
onComplete={() => {
refresh();
setOpen(false);
}}
/>
)}
{edit && (
<EditFlowModal
flow={flow!}
toggleDialog={() => {
setEdit(!edit);
refresh();
}}
/>
)}
<DeleteFlowConfirm />
<ViewHeader
titleKey={toUpperCase(flow?.alias || "")}
badges={[
@ -213,6 +316,7 @@ export const FlowDetails = () => {
}
: {},
]}
dropdownItems={dropdownItems}
/>
<PageSection variant="light">
{hasExecutions && (
@ -279,6 +383,7 @@ export const FlowDetails = () => {
<>
{executionList.expandableList.map((execution) => (
<FlowRow
builtIn={!!builtIn}
key={execution.id}
execution={execution}
onRowClick={(execution) => {

View file

@ -26,6 +26,7 @@ import { EditFlowDropdown } from "./EditFlowDropdown";
import "./flow-row.css";
type FlowRowProps = {
builtIn: boolean;
execution: ExpandableExecution;
onRowClick: (execution: ExpandableExecution) => void;
onRowChange: (execution: AuthenticationExecutionInfoRepresentation) => void;
@ -38,6 +39,7 @@ type FlowRowProps = {
};
export const FlowRow = ({
builtIn,
execution,
onRowClick,
onRowChange,
@ -101,20 +103,22 @@ export const FlowRow = ({
{execution.configurable && (
<ExecutionConfigModal execution={execution} />
)}
{execution.authenticationFlow && (
{execution.authenticationFlow && !builtIn && (
<EditFlowDropdown
execution={execution}
onAddExecution={onAddExecution}
onAddFlow={onAddFlow}
/>
)}
<Button
variant="plain"
aria-label={t("common:delete")}
onClick={onDelete}
>
<TrashIcon />
</Button>
{!builtIn && (
<Button
variant="plain"
aria-label={t("common:delete")}
onClick={onDelete}
>
<TrashIcon />
</Button>
)}
</DataListCell>,
]}
/>
@ -124,6 +128,7 @@ export const FlowRow = ({
hasSubList &&
execution.executionList?.map((execution) => (
<FlowRow
builtIn={builtIn}
key={execution.id}
execution={execution}
onRowClick={onRowClick}

View file

@ -3,6 +3,7 @@ export default {
title: "Authentication",
flows: "Flows",
flowName: "Flow name",
searchForFlow: "Search for flow",
usedBy: "Used by",
buildIn: "Built-in",
appliedByProviders: "Applied by the following providers",
@ -12,6 +13,10 @@ export default {
default: "Default",
notInUse: "Not in use",
duplicate: "Duplicate",
setAsDefault: "Set as default",
editInfo: "Edit info",
editFlow: "Edit flow",
edit: "Edit",
deleteConfirmFlow: "Delete flow?",
deleteConfirmFlowMessage:
'Are you sure you want to permanently delete the flow "<1>{{flow}}</1>".',