Skip to content

feat: show warning dialog if user switches off assign default org #15848

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion site/e2e/tests/deployment/idpOrgSync.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ test.describe("IdpOrgSyncPage", () => {
).toBeVisible();
});

test("toggle default organization assignment", async ({ page }) => {
test("toggle off default organization assignment", async ({ page }) => {
requiresLicense();
await page.goto("/deployment/idp-org-sync", {
waitUntil: "domcontentloaded",
Expand All @@ -89,6 +89,12 @@ test.describe("IdpOrgSyncPage", () => {
});
await toggle.click();

const dialog = page.getByRole("dialog");
await expect(dialog).toBeVisible();

await dialog.getByRole("button", { name: "Confirm" }).click();
await expect(dialog).not.toBeVisible();

await expect(
page.getByText("Organization sync settings updated."),
).toBeVisible();
Expand Down
7 changes: 6 additions & 1 deletion site/src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ import { type FC, forwardRef } from "react";
import { cn } from "utils/cn";

export const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-content-link disabled:pointer-events-none disabled:text-content-disabled [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 font-semibold border-solid cursor-pointer",
`inline-flex items-center justify-center gap-2 whitespace-nowrap
border-solid rounded-md transition-colors
text-sm font-semibold font-medium cursor-pointer
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-content-link
disabled:pointer-events-none disabled:text-content-disabled
[&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0`,
{
variants: {
variant: {
Expand Down
31 changes: 10 additions & 21 deletions site/src/components/Dialog/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
* @see {@link https://ui.shadcn.com/docs/components/dialog}
*/
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { X } from "lucide-react";
import {
type ComponentPropsWithoutRef,
type ElementRef,
Expand Down Expand Up @@ -46,29 +45,19 @@ export const DialogContent = forwardRef<
<DialogPrimitive.Content
ref={ref}
className={cn(
`fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg
gap-4 border bg-background p-6 shadow-lg duration-200 sm:rounded-lg
translate-x-[-50%] translate-y-[-50%]
data-[state=open]:animate-in data-[state=closed]:animate-out
data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0
data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95
data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%]
data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]`,
`fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg gap-4
border border-solid border-border bg-surface-primary p-8 shadow-lg duration-200 sm:rounded-lg
translate-x-[-50%] translate-y-[-50%]
data-[state=open]:animate-in data-[state=closed]:animate-out
data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0
data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95
data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%]
data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]`,
className,
)}
{...props}
>
{children}
<DialogPrimitive.Close
className={`absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity
hover:opacity-100
focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2
disabled:pointer-events-none
data-[state=open]:bg-surface-secondary data-[state=open]:text-content-disabled`}
>
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
));
Expand Down Expand Up @@ -106,7 +95,7 @@ export const DialogTitle = forwardRef<
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
"text-xl m-0 text-content-primary font-semibold leading-none tracking-tight",
className,
)}
{...props}
Expand All @@ -119,7 +108,7 @@ export const DialogDescription = forwardRef<
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-content-disabled", className)}
className={cn("text-sm text-content-secondary", className)}
{...props}
/>
));
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react";
import { userEvent, within } from "@storybook/test";
import {
MockOrganization,
MockOrganization2,
Expand Down Expand Up @@ -48,3 +49,18 @@ export const MissingGroups: Story = {
organizationSyncSettings: MockOrganizationSyncSettings,
},
};

export const AssignDefaultOrgWarningDialog: Story = {
args: {
organizationSyncSettings: MockOrganizationSyncSettings,
organizations: [MockOrganization, MockOrganization2],
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.click(
canvas.getByRole("switch", {
name: "Assign Default Organization",
}),
);
},
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import Skeleton from "@mui/material/Skeleton";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
Expand All @@ -12,6 +11,14 @@ import type {
import { ErrorAlert } from "components/Alert/ErrorAlert";
import { Button } from "components/Button/Button";
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "components/Dialog/Dialog";
import { EmptyState } from "components/EmptyState/EmptyState";
import {
HelpTooltip,
Expand All @@ -26,10 +33,6 @@ import {
type Option,
} from "components/MultiSelectCombobox/MultiSelectCombobox";
import { Switch } from "components/Switch/Switch";
import {
TableLoaderSkeleton,
TableRowSkeleton,
} from "components/TableLoader/TableLoader";
import { useFormik } from "formik";
import { Plus, SquareArrowOutUpRight, Trash } from "lucide-react";
import { type FC, useState } from "react";
Expand Down Expand Up @@ -74,6 +77,7 @@ export const IdpOrgSyncPageView: FC<IdpSyncPageViewProps> = ({
const organizationMappingCount = form.values.mapping
? Object.entries(form.values.mapping).length
: 0;
const [isDialogOpen, setIsDialogOpen] = useState(false);

const getOrgNames = (orgIds: readonly string[]) => {
return orgIds.map(
Expand Down Expand Up @@ -136,11 +140,15 @@ export const IdpOrgSyncPageView: FC<IdpSyncPageViewProps> = ({
id={ORGANIZATION_ASSIGN_DEFAULT_ID}
checked={form.values.organization_assign_default}
onCheckedChange={async (checked) => {
void form.setFieldValue(
"organization_assign_default",
checked,
);
form.handleSubmit();
if (!checked) {
setIsDialogOpen(true);
} else {
void form.setFieldValue(
"organization_assign_default",
checked,
);
form.handleSubmit();
}
}}
/>
<span className="flex flex-row items-center gap-1">
Expand Down Expand Up @@ -234,6 +242,36 @@ export const IdpOrgSyncPageView: FC<IdpSyncPageViewProps> = ({
</div>
</fieldset>
</form>

<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogContent className="flex flex-col gap-12 max-w-lg">
<DialogHeader className="flex flex-col gap-4">
<DialogTitle>
Switch off default organization assignment
</DialogTitle>
<DialogDescription>
Warning: This will remove all users from the default organization
unless otherwise specified in an organization mapping defined
below.
</DialogDescription>
</DialogHeader>
<DialogFooter className="flex flex-row">
<Button variant="outline" onClick={() => setIsDialogOpen(false)}>
Cancel
</Button>
<Button
onClick={() => {
void form.setFieldValue("organization_assign_default", false);
setIsDialogOpen(false);
form.handleSubmit();
}}
type="submit"
>
Confirm
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
};
Expand Down Expand Up @@ -318,31 +356,14 @@ const OrganizationRow: FC<OrganizationRowProps> = ({
);
};

const TableLoader = () => {
return (
<TableLoaderSkeleton>
<TableRowSkeleton>
<TableCell>
<Skeleton variant="text" width="25%" />
</TableCell>
<TableCell>
<Skeleton variant="text" width="25%" />
</TableCell>
<TableCell>
<Skeleton variant="text" width="10%" />
</TableCell>
</TableRowSkeleton>
</TableLoaderSkeleton>
);
};

export const AssignDefaultOrgHelpTooltip: FC = () => {
return (
<HelpTooltip>
<HelpTooltipTrigger />
<HelpTooltipContent>
<HelpTooltipText>
Disabling will remove all users from the default organization.
Disabling will remove all users from the default organization if a
mapping for the default organization is not defined.
</HelpTooltipText>
</HelpTooltipContent>
</HelpTooltip>
Expand Down
Loading