Skip to content

Commit

Permalink
Merge pull request #1849 from huwshimi/permissions-hook
Browse files Browse the repository at this point in the history
WD-19117 - refactor: create hook for handling fetching permission
  • Loading branch information
Ninfa-Jeon authored Feb 7, 2025
2 parents 2410cdc + 7758682 commit 44d98b1
Show file tree
Hide file tree
Showing 7 changed files with 743 additions and 258 deletions.
109 changes: 0 additions & 109 deletions src/components/PrimaryNav/PrimaryNav.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { screen, waitFor, within } from "@testing-library/react";
import { vi } from "vitest";

import { JIMMRelation, JIMMTarget } from "juju/jimm/JIMMV4";
import { actions as jujuActions } from "store/juju";
import {
authUserInfoFactory,
configFactory,
Expand Down Expand Up @@ -413,111 +412,3 @@ it("should show Permissions navigation button if the controller supports it", ()
screen.getByRole("link", { name: Label.PERMISSIONS }),
).toBeInTheDocument();
});

it("should fetch permissions", () => {
vi.spyOn(jujuActions, "checkRelation").mockReturnValue({
type: "juju/checkRelation",
payload: {
tuple: {
object: "user-eggman@external",
relation: JIMMRelation.AUDIT_LOG_VIEWER,
target_object: JIMMTarget.JIMM_CONTROLLER,
},
wsControllerURL: "wss://controller.example.com",
},
});
const state = rootStateFactory.build({
general: generalStateFactory.build({
config: configFactory.build({
controllerAPIEndpoint: "wss://controller.example.com",
isJuju: false,
}),
controllerConnections: {
"wss://controller.example.com": {
user: authUserInfoFactory.build(),
},
},
controllerFeatures: controllerFeaturesStateFactory.build({
"wss://controller.example.com": controllerFeaturesFactory.build({
auditLogs: true,
rebac: true,
}),
}),
}),
juju: jujuStateFactory.build({
rebacRelations: [],
}),
});
renderComponent(<PrimaryNav />, { state });
expect(jujuActions.checkRelation).toHaveBeenCalledWith({
tuple: {
object: "user-eggman@external",
relation: JIMMRelation.AUDIT_LOG_VIEWER,
target_object: JIMMTarget.JIMM_CONTROLLER,
},
wsControllerURL: "wss://controller.example.com",
});
expect(jujuActions.checkRelation).toHaveBeenCalledWith({
tuple: {
object: "user-eggman@external",
relation: JIMMRelation.ADMINISTRATOR,
target_object: JIMMTarget.JIMM_CONTROLLER,
},
wsControllerURL: "wss://controller.example.com",
});
});

it("should not fetch permissions if they've already been loaded", () => {
vi.spyOn(jujuActions, "checkRelation").mockReturnValue({
type: "juju/checkRelation",
payload: {
tuple: {
object: "user-eggman@external",
relation: JIMMRelation.AUDIT_LOG_VIEWER,
target_object: JIMMTarget.JIMM_CONTROLLER,
},
wsControllerURL: "wss://controller.example.com",
},
});
const state = rootStateFactory.build({
general: generalStateFactory.build({
config: configFactory.build({
controllerAPIEndpoint: "wss://controller.example.com",
isJuju: false,
}),
controllerConnections: {
"wss://controller.example.com": {
user: authUserInfoFactory.build(),
},
},
controllerFeatures: controllerFeaturesStateFactory.build({
"wss://controller.example.com": controllerFeaturesFactory.build({
auditLogs: true,
rebac: true,
}),
}),
}),
juju: jujuStateFactory.build({
rebacRelations: [
rebacRelationFactory.build({
tuple: relationshipTupleFactory.build({
object: "user-eggman@external",
relation: JIMMRelation.AUDIT_LOG_VIEWER,
target_object: JIMMTarget.JIMM_CONTROLLER,
}),
allowed: true,
}),
rebacRelationFactory.build({
tuple: relationshipTupleFactory.build({
object: "user-eggman@external",
relation: JIMMRelation.ADMINISTRATOR,
target_object: JIMMTarget.JIMM_CONTROLLER,
}),
allowed: true,
}),
],
}),
});
renderComponent(<PrimaryNav />, { state });
expect(jujuActions.checkRelation).not.toHaveBeenCalled();
});
102 changes: 7 additions & 95 deletions src/components/PrimaryNav/PrimaryNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,25 @@ import type { NavItem } from "@canonical/react-components/dist/components/SideNa
import { urls as generateReBACURLS } from "@canonical/rebac-admin";
import type { HTMLProps, ReactNode } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { useSelector } from "react-redux";
import type { NavLinkProps } from "react-router";
import { NavLink } from "react-router";

import UserMenu from "components/UserMenu/UserMenu";
import { DARK_THEME } from "consts";
import { JIMMRelation, JIMMTarget } from "juju/jimm/JIMMV4";
import {
useAuditLogsPermitted,
useIsJIMMAdmin,
} from "juju/api-hooks/permissions";
import {
getAppVersion,
isAuditLogsEnabled,
isCrossModelQueriesEnabled,
getVisitURLs,
isReBACEnabled,
getActiveUserTag,
getWSControllerURL,
} from "store/general/selectors";
import { actions as jujuActions } from "store/juju";
import {
getControllerData,
getGroupedModelStatusCounts,
getReBACPermission,
} from "store/juju/selectors";
import type { Controllers } from "store/juju/types";
import { useAppSelector } from "store/store";
Expand Down Expand Up @@ -88,45 +86,16 @@ const useControllersLink = () => {
};

const PrimaryNav = () => {
const dispatch = useDispatch();
const appVersion = useSelector(getAppVersion);
const [updateAvailable, setUpdateAvailable] = useState(false);
const versionRequested = useRef(false);
const crossModelQueriesEnabled = useAppSelector(isCrossModelQueriesEnabled);
const auditLogsEnabled = useAppSelector(isAuditLogsEnabled);
const rebacEnabled = useAppSelector(isReBACEnabled);
const { blocked: blockedModels } = useSelector(getGroupedModelStatusCounts);
const wsControllerURL = useAppSelector(getWSControllerURL);
const activeUser = useAppSelector((state) =>
getActiveUserTag(state, wsControllerURL),
);
const jimmControllerAdminPermission = useAppSelector(
(state) =>
activeUser &&
getReBACPermission(state, {
object: activeUser,
relation: JIMMRelation.ADMINISTRATOR,
target_object: JIMMTarget.JIMM_CONTROLLER,
}),
);
const auditLogsPermission = useAppSelector(
(state) =>
activeUser &&
getReBACPermission(state, {
object: activeUser,
relation: JIMMRelation.AUDIT_LOG_VIEWER,
target_object: JIMMTarget.JIMM_CONTROLLER,
}),
);
const { permitted: isJIMMControllerAdmin } = useIsJIMMAdmin();
const { permitted: auditLogsAllowed } = useAuditLogsPermitted();
const controllersLink = useControllersLink();
const isJIMMControllerAdmin =
jimmControllerAdminPermission && jimmControllerAdminPermission.allowed;
const rebacAllowed = rebacEnabled && isJIMMControllerAdmin;
const auditLogsAllowed =
auditLogsEnabled &&
auditLogsPermission &&
auditLogsPermission.allowed &&
isJIMMControllerAdmin;

useEffect(() => {
if (appVersion && !versionRequested.current) {
Expand All @@ -137,63 +106,6 @@ const PrimaryNav = () => {
}
}, [appVersion]);

useEffect(() => {
if (
// Don't fetch it if it's already in the store.
!jimmControllerAdminPermission &&
wsControllerURL &&
activeUser &&
// Only check the relation if the controller supports audit logs or ReBAC.
(rebacEnabled || auditLogsEnabled)
) {
dispatch(
jujuActions.checkRelation({
tuple: {
object: activeUser,
relation: JIMMRelation.ADMINISTRATOR,
target_object: JIMMTarget.JIMM_CONTROLLER,
},
wsControllerURL,
}),
);
}
}, [
activeUser,
auditLogsEnabled,
dispatch,
jimmControllerAdminPermission,
rebacEnabled,
wsControllerURL,
]);

useEffect(() => {
if (
// Don't fetch it if it's already in the store.
!auditLogsPermission &&
wsControllerURL &&
activeUser &&
// Only check the relation if the controller supports audit logs.
auditLogsEnabled
) {
dispatch(
jujuActions.checkRelation({
tuple: {
object: activeUser,
relation: JIMMRelation.AUDIT_LOG_VIEWER,
target_object: JIMMTarget.JIMM_CONTROLLER,
},
wsControllerURL,
}),
);
}
}, [
activeUser,
auditLogsEnabled,
dispatch,
auditLogsPermission,
wsControllerURL,
]);

const navigation: NavItem<NavLinkProps>[] = [
{
component: NavLink,
Expand Down
1 change: 1 addition & 0 deletions src/juju/api-hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ export {
useRevokeSecret,
type Label as SecretsLabel,
} from "./secrets";
export { useCheckPermissions } from "./permissions";
Loading

0 comments on commit 44d98b1

Please sign in to comment.