Skip to content

Commit

Permalink
Merge branch 'main' into provider-forms-tests
Browse files Browse the repository at this point in the history
  • Loading branch information
huwshimi authored Jan 9, 2025
2 parents 2139397 + 46cd0e4 commit de14a7a
Show file tree
Hide file tree
Showing 19 changed files with 530 additions and 198 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ on:

jobs:
build:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
outputs:
rock: ${{ steps.set.outputs.rock }}
steps:
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## [1.22.1](https://github.com/canonical/identity-platform-admin-ui/compare/v1.22.0...v1.22.1) (2024-12-12)


### Bug Fixes

* addressing CVE-2024-45337 ([a53fc20](https://github.com/canonical/identity-platform-admin-ui/commit/a53fc2048d683fb3b9e5cc23b494a40fdbe00d58)), closes [#505](https://github.com/canonical/identity-platform-admin-ui/issues/505)

## [1.22.0](https://github.com/canonical/identity-platform-admin-ui/compare/v1.21.0...v1.22.0) (2024-12-04)


Expand Down
10 changes: 5 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,13 @@ require (
go.opentelemetry.io/otel/metric v1.19.0 // indirect
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/crypto v0.19.0 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/exp v0.0.0-20231226003508-02704c960a9b // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/term v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/term v0.27.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/time v0.5.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917 // indirect
Expand Down
11 changes: 11 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/exp v0.0.0-20231226003508-02704c960a9b h1:kLiC65FbiHWFAOu+lxwNPujcsl8VYyTYYEZnsOO1WK4=
golang.org/x/exp v0.0.0-20231226003508-02704c960a9b/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
Expand All @@ -242,6 +244,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand All @@ -251,16 +255,22 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
Expand All @@ -270,6 +280,7 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM=
golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
Expand Down
2 changes: 1 addition & 1 deletion internal/version/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@

package version

const Version = "1.22.0" // x-release-please-version
const Version = "1.22.1" // x-release-please-version
2 changes: 1 addition & 1 deletion rockcraft.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: identity-platform-admin-ui

base: bare
build-base: [email protected]
version: '1.22.0' # x-release-please-version
version: '1.22.1' # x-release-please-version
summary: Canonical Identity platform Admin UI
description: |
This is the Canonical Identity platform admin UI used for connecting
Expand Down
224 changes: 224 additions & 0 deletions ui/src/components/DeletePanelButton/DeletePanelButton.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
import { screen, waitFor } from "@testing-library/dom";
import * as reactQuery from "@tanstack/react-query";
import userEvent from "@testing-library/user-event";
import {
NotificationConsumer,
NotificationProvider,
} from "@canonical/react-components";

import { renderComponent } from "test/utils";

import DeletePanelButton from "./DeletePanelButton";
import { Label } from "./types";
import { Location } from "react-router-dom";

vi.mock("@tanstack/react-query", async () => {
const actual = await vi.importActual("@tanstack/react-query");
return {
...actual,
useQueryClient: vi.fn(),
};
});

beforeEach(() => {
vi.spyOn(reactQuery, "useQueryClient").mockReturnValue({
invalidateQueries: vi.fn(),
} as unknown as reactQuery.QueryClient);
});

test("displays the delete button", () => {
renderComponent(
<DeletePanelButton
confirmButtonLabel="Confirm"
confirmContent="Content"
entityName="Nebulous"
invalidateQuery="nebulous"
onDelete={() => Promise.resolve()}
successPath="/nebulous"
successMessage="successfully formed"
/>,
);
expect(
screen.getByRole("button", { name: Label.DELETE }),
).toBeInTheDocument();
});

test("displays a confirmation", async () => {
renderComponent(
<DeletePanelButton
confirmButtonLabel="Confirm"
confirmContent="Content"
confirmTitle="Define"
entityName="Nebulous"
invalidateQuery="nebulous"
onDelete={() => Promise.resolve()}
successPath="/nebulous"
successMessage="successfully formed"
/>,
);
await userEvent.click(screen.getByRole("button", { name: Label.DELETE }));
expect(screen.getByRole("dialog", { name: "Define" })).toBeInTheDocument();
});

test("can disable the confirm button", async () => {
renderComponent(
<DeletePanelButton
confirmButtonLabel="Confirm"
confirmButtonDisabled
confirmContent="Content"
entityName="Nebulous"
invalidateQuery="nebulous"
onDelete={() => Promise.resolve()}
successPath="/nebulous"
successMessage="successfully formed"
/>,
);
await userEvent.click(screen.getByRole("button", { name: Label.DELETE }));
expect(screen.getByRole("button", { name: "Confirm" })).toBeDisabled();
});

test("starts deletion", async () => {
renderComponent(
<DeletePanelButton
confirmButtonLabel="Confirm"
confirmContent="Content"
entityName="Nebulous"
invalidateQuery="nebulous"
onDelete={() => Promise.resolve()}
successPath="/nebulous"
successMessage="successfully formed"
/>,
);
await userEvent.click(screen.getByRole("button", { name: Label.DELETE }));
await userEvent.click(screen.getByRole("button", { name: "Confirm" }));
expect(document.querySelector(".u-animation--spin")).toBeInTheDocument();
});

test("calls the delete method", async () => {
const onDelete = vi.fn().mockImplementation(() => Promise.resolve());
renderComponent(
<DeletePanelButton
confirmButtonLabel="Confirm"
confirmContent="Content"
entityName="Nebulous"
invalidateQuery="nebulous"
onDelete={onDelete}
successPath="/nebulous"
successMessage="successfully formed"
/>,
);
await userEvent.click(screen.getByRole("button", { name: Label.DELETE }));
await userEvent.click(screen.getByRole("button", { name: "Confirm" }));
expect(onDelete).toHaveBeenCalled();
});

test("handles a successful delete call", async () => {
let location: Location | null = null;
renderComponent(
<NotificationProvider>
<NotificationConsumer />
<DeletePanelButton
confirmButtonLabel="Confirm"
confirmContent="Content"
entityName="Nebulous"
invalidateQuery="nebulous"
onDelete={() => Promise.resolve()}
successPath="/nebulous"
successMessage="successfully formed"
/>
</NotificationProvider>,
{
setLocation: (newLocation) => {
location = newLocation;
},
},
);
await userEvent.click(screen.getByRole("button", { name: Label.DELETE }));
await userEvent.click(screen.getByRole("button", { name: "Confirm" }));
expect(
screen
.getByText("successfully formed")
.closest(".p-notification--positive"),
).toBeInTheDocument();
expect((location as Location | null)?.pathname).toBe("/nebulous");
});

test("notifies on error", async () => {
renderComponent(
<NotificationProvider>
<NotificationConsumer />
<DeletePanelButton
confirmButtonLabel="Confirm"
confirmContent="Content"
entityName="Nebulous"
invalidateQuery="nebulous"
onDelete={() => Promise.reject("Oops")}
successPath="/nebulous"
successMessage="successfully formed"
/>
</NotificationProvider>,
);
await userEvent.click(screen.getByRole("button", { name: Label.DELETE }));
await userEvent.click(screen.getByRole("button", { name: "Confirm" }));
expect(
screen
.getByText("Nebulous deletion failed")
.closest(".p-notification--negative"),
).toBeInTheDocument();
expect(screen.getByText("Oops")).toHaveClass("p-notification__message");
});

test("notifies on error object", async () => {
renderComponent(
<NotificationProvider>
<NotificationConsumer />
<DeletePanelButton
confirmButtonLabel="Confirm"
confirmContent="Content"
entityName="Nebulous"
invalidateQuery="nebulous"
onDelete={() => Promise.reject(new Error("Oops"))}
successPath="/nebulous"
successMessage="successfully formed"
/>
</NotificationProvider>,
);
await userEvent.click(screen.getByRole("button", { name: Label.DELETE }));
await userEvent.click(screen.getByRole("button", { name: "Confirm" }));
expect(
screen
.getByText("Nebulous deletion failed")
.closest(".p-notification--negative"),
).toBeInTheDocument();
expect(screen.getByText("Oops")).toHaveClass("p-notification__message");
});

test("invlidates queries and hides the spinner on success", async () => {
const invalidateQueries = vi.fn();
vi.spyOn(reactQuery, "useQueryClient").mockReturnValue({
invalidateQueries,
} as unknown as reactQuery.QueryClient);
renderComponent(
<DeletePanelButton
confirmButtonLabel="Confirm"
confirmContent="Content"
entityName="Nebulous"
invalidateQuery="nebulous"
onDelete={() => Promise.resolve()}
successPath="/nebulous"
successMessage="successfully formed"
/>,
);
await userEvent.click(screen.getByRole("button", { name: Label.DELETE }));
await userEvent.click(screen.getByRole("button", { name: "Confirm" }));
await waitFor(() =>
expect(invalidateQueries).toHaveBeenCalledWith({
queryKey: ["nebulous"],
}),
);
await waitFor(() =>
expect(
document.querySelector(".u-animation--spin"),
).not.toBeInTheDocument(),
);
});
62 changes: 62 additions & 0 deletions ui/src/components/DeletePanelButton/DeletePanelButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { FC, useState } from "react";
import { useNavigate } from "react-router-dom";
import { useQueryClient } from "@tanstack/react-query";
import { ConfirmationButton, useNotify } from "@canonical/react-components";
import { Label, Props } from "./types";

const DeletePanelButton: FC<Props> = ({
confirmButtonDisabled,
confirmButtonLabel,
confirmContent,
confirmTitle = "Confirm delete",
invalidateQuery,
entityName,
onDelete,
successMessage,
successPath,
}) => {
const notify = useNotify();
const queryClient = useQueryClient();
const [isLoading, setLoading] = useState(false);
const navigate = useNavigate();

const handleDelete = () => {
setLoading(true);
onDelete()
.then(() => {
navigate(successPath, notify.queue(notify.success(successMessage)));
})
.catch((error: unknown) => {
notify.failure(
`${entityName} deletion failed`,
error instanceof Error ? error : null,
typeof error === "string" ? error : null,
);
})
.finally(() => {
setLoading(false);
void queryClient.invalidateQueries({
queryKey: [invalidateQuery],
});
});
};

return (
<ConfirmationButton
className="u-no-margin--bottom"
loading={isLoading}
confirmationModalProps={{
children: confirmContent,
confirmButtonDisabled,
confirmButtonLabel,
onConfirm: handleDelete,
title: confirmTitle,
}}
title={confirmTitle}
>
{Label.DELETE}
</ConfirmationButton>
);
};

export default DeletePanelButton;
2 changes: 2 additions & 0 deletions ui/src/components/DeletePanelButton/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default } from "./DeletePanelButton";
export { Label as DeletePanelButtonLabel } from "./types";
Loading

0 comments on commit de14a7a

Please sign in to comment.