Skip to content

Commit e4a341d

Browse files
committed
MAT-9817: add madie-admin library share
1 parent 640eb36 commit e4a341d

13 files changed

Lines changed: 2778 additions & 49 deletions

File tree

package-lock.json

Lines changed: 51 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
"@tanstack/react-table": "^8.21.3",
8383
"ace-builds": "^1.44.0",
8484
"axios": "^1.17.0",
85+
"file-saver": "^2.0.5",
8586
"formik": "^2.4.9",
8687
"lodash": "^4.18.1",
8788
"react": "^17.0.2",
@@ -91,7 +92,8 @@
9192
"single-spa": "^5.9.3",
9293
"single-spa-react": "^4.2.0",
9394
"tailwindcss": "^3.4.1",
94-
"twin.macro": "^3.4.1"
95+
"twin.macro": "^3.4.1",
96+
"yup": "^1.7.1"
9597
},
9698
"types": "dist/madie-madie-admin.d.ts",
9799
"overrides": {

src/components/landing/userManagement/userProfile/UserProfile.test.tsx

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ jest.mock("@madie/madie-util", () => ({
3737
getUser: (...args: unknown[]) => mockGetUser(...args),
3838
getBulkUserDetails: (...args: unknown[]) => mockGetBulkUserDetails(...args),
3939
})),
40+
useOktaTokens: jest.fn().mockReturnValue({
41+
getAccessToken: () => "test-token",
42+
getUserName: () => "testUser",
43+
}),
44+
useUserRoles: jest
45+
.fn()
46+
.mockReturnValue({ roles: ["MADiE-Admin"], isAdmin: true }),
4047
useMeasureServiceApi: jest.fn(() => ({
4148
adminSearchMeasuresForUser: (...args: unknown[]) =>
4249
mockAdminSearchMeasures(...args),
@@ -152,6 +159,35 @@ jest.mock("@madie/madie-util", () => ({
152159
</div>
153160
) : null,
154161
}));
162+
jest.mock(
163+
"./actionCenter/LibraryShareDialog/LibraryShareDialog",
164+
() => (props: any) =>
165+
props.open ? (
166+
<div data-testid="library-share-dialog" data-option={props.option}>
167+
Library Share Dialog
168+
<button
169+
data-testid="library-share-success-btn"
170+
onClick={() =>
171+
props.onClose("success", "Library Successfully Shared")
172+
}
173+
>
174+
Success
175+
</button>
176+
<button
177+
data-testid="library-share-danger-btn"
178+
onClick={() => props.onClose("danger", "Unable to share library")}
179+
>
180+
Danger
181+
</button>
182+
<button
183+
data-testid="library-share-close-btn"
184+
onClick={() => props.onClose()}
185+
>
186+
Close
187+
</button>
188+
</div>
189+
) : null
190+
);
155191

156192
const renderAt = (initialEntry: string) =>
157193
render(
@@ -2569,6 +2605,92 @@ describe("UserProfile", () => {
25692605
});
25702606
});
25712607

2608+
describe("library share actions", () => {
2609+
it("opens LibraryShareDialog when Share With is selected for a library", async () => {
2610+
mockAdminSearchMeasures.mockResolvedValue(pageWith([ownedMeasure], 1));
2611+
mockFetchCqlLibraries.mockResolvedValue(pageWith([ownedLibrary], 1));
2612+
2613+
renderAt("/admin/userProfile/test_user");
2614+
2615+
await userEvent.click(await screen.findByTestId("owned-libraries-tab"));
2616+
2617+
await userEvent.click(await screen.findByTestId("checkbox-lib1"));
2618+
2619+
await userEvent.click(await screen.findByTestId("share-action-btn"));
2620+
2621+
await userEvent.click(await screen.findByTestId("Share With-option"));
2622+
2623+
expect(
2624+
await screen.findByTestId("library-share-dialog")
2625+
).toBeInTheDocument();
2626+
2627+
expect(screen.getByTestId("library-share-dialog")).toHaveAttribute(
2628+
"data-option",
2629+
"Share With"
2630+
);
2631+
});
2632+
it("handles successful library share dialog close and displays success toast", async () => {
2633+
mockAdminSearchMeasures.mockResolvedValue(pageWith([ownedMeasure], 1));
2634+
mockFetchCqlLibraries.mockResolvedValue(pageWith([ownedLibrary], 1));
2635+
2636+
renderAt("/admin/userProfile/test_user");
2637+
2638+
await userEvent.click(await screen.findByTestId("owned-libraries-tab"));
2639+
2640+
await userEvent.click(await screen.findByTestId("checkbox-lib1"));
2641+
2642+
await userEvent.click(await screen.findByTestId("share-action-btn"));
2643+
2644+
await userEvent.click(await screen.findByTestId("Share With-option"));
2645+
2646+
const callsBefore = mockFetchCqlLibraries.mock.calls.length;
2647+
2648+
await userEvent.click(
2649+
await screen.findByTestId("library-share-success-btn")
2650+
);
2651+
2652+
expect(
2653+
screen.queryByTestId("library-share-dialog")
2654+
).not.toBeInTheDocument();
2655+
2656+
expect(
2657+
await screen.findByText("Library Successfully Shared")
2658+
).toBeInTheDocument();
2659+
2660+
await waitFor(() => {
2661+
expect(mockFetchCqlLibraries.mock.calls.length).toBeGreaterThan(
2662+
callsBefore
2663+
);
2664+
});
2665+
});
2666+
it("handles library share dialog close with a non-success message", async () => {
2667+
mockAdminSearchMeasures.mockResolvedValue(pageWith([ownedMeasure], 1));
2668+
mockFetchCqlLibraries.mockResolvedValue(pageWith([ownedLibrary], 1));
2669+
2670+
renderAt("/admin/userProfile/test_user");
2671+
2672+
await userEvent.click(await screen.findByTestId("owned-libraries-tab"));
2673+
2674+
await userEvent.click(await screen.findByTestId("checkbox-lib1"));
2675+
2676+
await userEvent.click(await screen.findByTestId("share-action-btn"));
2677+
2678+
await userEvent.click(await screen.findByTestId("Share With-option"));
2679+
2680+
await userEvent.click(
2681+
await screen.findByTestId("library-share-danger-btn")
2682+
);
2683+
2684+
expect(
2685+
screen.queryByTestId("library-share-dialog")
2686+
).not.toBeInTheDocument();
2687+
2688+
expect(
2689+
await screen.findByText("Unable to share library")
2690+
).toBeInTheDocument();
2691+
});
2692+
});
2693+
25722694
describe("transfer action", () => {
25732695
const ownedMeasureRow = {
25742696
id: "m1",

src/components/landing/userManagement/userProfile/UserProfile.tsx

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ import {
5252
} from "../../../../icons/MeasureListTableRightArrowIcons";
5353
import ActionCenter from "./actionCenter/ActionCenter";
5454
import "./UserProfile.scss";
55+
import LibraryActionCenter from "./actionCenter/LibraryActionCenter";
56+
import LibraryShareDialog from "./actionCenter/LibraryShareDialog/LibraryShareDialog";
57+
import _ from "lodash";
5558

5659
type Ownership =
5760
| "OWNED_MEASURE"
@@ -1455,17 +1458,15 @@ const UserProfile = () => {
14551458
.map((row) => row.actions);
14561459
return [...topLevel, ...expanded];
14571460
}, [selectedTopLevelRows, expandedRows, selectedExpandedRowIds]);
1458-
const selectedLibraries = useMemo(() => {
1459-
const topLevel = libraryTable
1461+
const selectedLibraries = [
1462+
...libraryTable
14601463
.getSelectedRowModel()
1461-
.rows.map((row) => row.original.actions);
1464+
.rows.map((row) => row.original.actions),
14621465

1463-
const expanded = expandedLibraryRows
1466+
...expandedLibraryRows
14641467
.filter((row) => selectedExpandedLibraryRowIds.includes(row.id))
1465-
.map((row) => row.actions);
1466-
1467-
return [...topLevel, ...expanded];
1468-
}, [libraryTable, expandedLibraryRows, selectedExpandedLibraryRowIds]);
1468+
.map((row) => row.actions),
1469+
];
14691470
const handleConfirmDeleteLibrary = useCallback(async () => {
14701471
if (!deleteTarget) return;
14711472

@@ -1608,6 +1609,35 @@ const UserProfile = () => {
16081609
[table, clearExpansion]
16091610
);
16101611

1612+
const [libraryShareDialogOpen, setLibraryShareDialogOpen] = useState(false);
1613+
1614+
const [libraryShareOption, setLibraryShareOption] = useState("");
1615+
1616+
const handleLibraryShare = useCallback((option: string) => {
1617+
setLibraryShareOption(option);
1618+
setLibraryShareDialogOpen(true);
1619+
}, []);
1620+
1621+
const handleLibraryShareDialogClose = useCallback(
1622+
(toastType?: "success" | "danger", toastMessage?: string) => {
1623+
setLibraryShareDialogOpen(false);
1624+
setLibraryShareOption("");
1625+
1626+
if (toastMessage) {
1627+
setToastType(toastType ?? "success");
1628+
setToastMessage(toastMessage);
1629+
setToastOpen(true);
1630+
}
1631+
1632+
if (toastType === "success") {
1633+
libraryTable.toggleAllRowsSelected(false);
1634+
clearLibraryExpansion();
1635+
setRefreshToken((t) => t + 1);
1636+
}
1637+
},
1638+
[libraryTable, clearLibraryExpansion]
1639+
);
1640+
16111641
return (
16121642
<div className="user-profile" data-testid="user-profile">
16131643
<div className="user-profile-header">
@@ -1672,13 +1702,13 @@ const UserProfile = () => {
16721702
className="search-filter-bar flex-end"
16731703
data-testid="search-filter-bar"
16741704
>
1675-
<ActionCenter
1676-
target="library"
1677-
measures={selectedLibraries}
1678-
canDelete={canDelete}
1705+
<LibraryActionCenter
1706+
libraries={selectedLibraries}
16791707
activeTab={activeTab}
16801708
onDelete={openDeleteDialog}
1709+
onShare={handleLibraryShare}
16811710
disabledReason={deleteDisabledReason}
1711+
canDelete={canDelete}
16821712
/>
16831713
</div>
16841714
)}
@@ -1798,6 +1828,12 @@ const UserProfile = () => {
17981828
unshareFromUser={harpId}
17991829
isAdmin
18001830
/>
1831+
<LibraryShareDialog
1832+
libraries={selectedLibraries}
1833+
open={libraryShareDialogOpen}
1834+
option={libraryShareOption}
1835+
onClose={handleLibraryShareDialogClose}
1836+
/>
18011837

18021838
<TransferDialog
18031839
measures={selectedMeasures}

0 commit comments

Comments
 (0)