Skip to content

Commit

Permalink
Evaluator role access (#296)
Browse files Browse the repository at this point in the history
  • Loading branch information
0tuedon authored Jul 3, 2024
1 parent aca1444 commit 1e24d52
Show file tree
Hide file tree
Showing 13 changed files with 122 additions and 68 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"no-console": ["warn", { "allow": ["warn", "error"] }],
"no-unused-vars": ["warn", { "vars": "all", "args": "none", "ignoreRestSiblings": false }],
"eol-last": ["error", "always"],
"react/react-in-jsx-scope": "off"
"react/react-in-jsx-scope": "off",
"react/display-name": "off"
}
}
21 changes: 10 additions & 11 deletions src/components/editTranscript/EditTranscript.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,8 @@ import SubmitTranscriptMenu, {
TranscriptSubmitOptions,
} from "../menus/SubmitTranscriptMenu";
import { TranscriptContent, UserReviewData } from "../../../types";
import { useSession } from "next-auth/react";
import { useUpdateTranscript } from "@/services/api/transcripts";

import { useHasPermission } from "@/hooks/useHasPermissions";
// Interfaces for react-markdown-editior
export interface IHandleEditorChange {
text: string;
Expand Down Expand Up @@ -74,15 +73,15 @@ const EditTranscript = ({
onOpen: () => void;
}) => {
const [isPreviewOnly, setIsPreviewOnly] = useState(false);
const [isModalOpen, setIsModalopen] = useState(false);
const [isModalOpen, setIsModalOpen] = useState(false);
const toast = useToast();
const mdParser = new MarkdownIt();

const reviewSubmissionDisabled =
!!reviewData.branchUrl && !!reviewData.pr_url;

const { data: userSession } = useSession();
const isAdmin = userSession?.user?.permissions === "admin";
const canSubmitToOwnRepo = useHasPermission("submitToOwnRepo");

const { isLoading: saveLoading } = useUpdateTranscript();

const handleSave = async () => {
Expand Down Expand Up @@ -149,7 +148,7 @@ const EditTranscript = ({
// restoreOriginal content function
const onClickRestore = () => {
restoreOriginal();
setIsModalopen(false);
setIsModalOpen(false);
};

return (
Expand Down Expand Up @@ -180,7 +179,7 @@ const EditTranscript = ({
</Button>
<Button
colorScheme="gray"
onClick={() => setIsModalopen(true)}
onClick={() => setIsModalOpen(true)}
size="xs"
ml="auto"
display="block"
Expand Down Expand Up @@ -215,10 +214,10 @@ const EditTranscript = ({
onClick={onOpen}
isDisabled={reviewSubmissionDisabled}
>
Submit {isAdmin ? `(${prRepo})` : ""}
Submit {canSubmitToOwnRepo ? `(${prRepo})` : ""}
</Button>
</Tooltip>
{isAdmin && (
{canSubmitToOwnRepo && (
<>
<SubmitTranscriptMenu setPrRepo={setPrRepo} />
</>
Expand All @@ -237,7 +236,7 @@ const EditTranscript = ({
/>
</Box>
</Box>
<Modal isOpen={isModalOpen} onClose={() => setIsModalopen(false)}>
<Modal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)}>
<ModalOverlay />
<ModalContent>
<ModalHeader>
Expand All @@ -253,7 +252,7 @@ const EditTranscript = ({
<Button
variant="outline"
size="sm"
onClick={() => setIsModalopen(false)}
onClick={() => setIsModalOpen(false)}
mr={2}
>
Cancel
Expand Down
12 changes: 9 additions & 3 deletions src/components/navbar/AdminMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,20 @@ import { Box, Divider, Flex, Heading, Icon } from "@chakra-ui/react";
import React from "react";
import { BiLockOpenAlt } from "react-icons/bi";

const AdminMenu = ({ children }: { children: React.ReactNode }) => {
const AdminMenu = ({
children,
role,
}: {
children: React.ReactNode;
role?: string;
}) => {
return (
<>
<Box my={4}>
<Divider />
<Flex my={4} gap={2} alignItems="center">
<Heading size="sm" color="gray.400">
Admin
<Heading size="sm" color="gray.400" textTransform={"capitalize"}>
{role}
</Heading>
<Icon color={"orange.400"} as={BiLockOpenAlt} />
</Flex>
Expand Down
44 changes: 25 additions & 19 deletions src/components/navbar/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@ import { FiUser, FiUsers } from "react-icons/fi";
import { HiOutlineBookOpen, HiOutlineSwitchHorizontal } from "react-icons/hi";
import MenuNav from "./MenuNav";
import AdminMenu from "./AdminMenu";
import { useHasPermission } from "@/hooks/useHasPermissions";

const Menu = () => {
const { data: userSession } = useSession();

const isAdmin = userSession?.user?.permissions === "admin";

// Permissions check
const canAccessAdminNav = useHasPermission("accessAdminNav");
const canAccessTransactions = useHasPermission("accessTransactions");
const canAccessUsers = useHasPermission("accessUsers");
const router = useRouter();
const currentRoute = router.asPath?.split("/")[1] ?? "";
const fullCurrentRoute = router.asPath;
Expand Down Expand Up @@ -164,30 +166,34 @@ const Menu = () => {
icon={BiWallet}
/>
</Flex>
{isAdmin ? (
<AdminMenu>
{canAccessAdminNav ? (
<AdminMenu role={userSession.user?.permissions}>
<Flex direction="column" gap={2}>
<MenuNav
currentRoute={fullCurrentRoute}
routeName={"Transactions"}
routeLink={ROUTES_CONFIG.TRANSACTIONS}
handleClose={closeMenu}
icon={HiOutlineSwitchHorizontal}
/>
{canAccessTransactions && (
<MenuNav
currentRoute={fullCurrentRoute}
routeName={"Transactions"}
routeLink={ROUTES_CONFIG.TRANSACTIONS}
handleClose={closeMenu}
icon={HiOutlineSwitchHorizontal}
/>
)}
<MenuNav
currentRoute={fullCurrentRoute}
routeName={ROUTES_CONFIG.REVIEWS}
routeLink={ROUTES_CONFIG.ALL_REVIEWS}
handleClose={closeMenu}
icon={CgTranscript}
/>
<MenuNav
currentRoute={fullCurrentRoute}
routeName={"Users"}
routeLink={ROUTES_CONFIG.USERS}
handleClose={closeMenu}
icon={FiUsers}
/>
{canAccessUsers && (
<MenuNav
currentRoute={fullCurrentRoute}
routeName={"Users"}
routeLink={ROUTES_CONFIG.USERS}
handleClose={closeMenu}
icon={FiUsers}
/>
)}
</Flex>
</AdminMenu>
) : null}
Expand Down
6 changes: 4 additions & 2 deletions src/components/tables/AdminReviewsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { useResetReview } from "@/services/api/reviews/useResetReviews";
import { dateFormatGeneral } from "@/utils";
import { getReviewStatus } from "@/utils/review";
import { format } from "date-fns";
import { useHasPermission } from "@/hooks/useHasPermissions";

const tableStructure = [
{
Expand Down Expand Up @@ -124,6 +125,7 @@ const AdminResetSelect = ({ children }: AdminResetSelectProps) => {
const [selectedIds, setSelectedIds] = useState<string[]>([]);
const toast = useToast();
const { data: userSession } = useSession();

const queryClient = useQueryClient();
const resetReview = useResetReview();
const handleCheckboxToggle = (values: (string | number)[]) => {
Expand Down Expand Up @@ -178,6 +180,7 @@ const AdminReviewsTable = ({
hasFilters,
reviews,
}: Props) => {
const canResetReviews = useHasPermission("resetReviews");
return (
<AdminResetSelect>
{({ handleReset, hasAdminSelected, isResetting }) => (
Expand All @@ -186,9 +189,8 @@ const AdminReviewsTable = ({
emptyView={<EmptyView hasFilters={hasFilters} />}
isLoading={isLoading}
isError={isError}
// takes action-loading as isArchiving
tableStructure={tableStructure}
showAdminControls
showAdminControls={canResetReviews}
actionItems={
<>
{hasAdminSelected && (
Expand Down
8 changes: 5 additions & 3 deletions src/components/tables/TableItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ import { resolveGHApiUrl } from "@/utils/github";
import { getReviewStatus } from "@/utils/review";
import { AdminReview } from "@/services/api/admin/useReviews";
import { format } from "date-fns";
import { UserRoles } from "@/config/default";
import { useHasPermission } from "@/hooks/useHasPermissions";
import { UserRoles } from "../../../types";

// eslint-disable-next-line no-unused-vars
const defaultUndefined = <TData, TCb extends (data: TData) => any>(
Expand Down Expand Up @@ -136,7 +137,8 @@ export const TableAction = <T extends object>({
const isAdminReviews = getReviewStatus(row as AdminReview);
const isUsersTable = tableItem.actionTableType === "user";

const isAdmin = userSession?.user?.permissions === "admin";
const isAdmin = useHasPermission("accessAdminNav");

const showCheckBox = isAdmin && showControls;

/* Forced the type here because it uses a dynamic type so Ts isn't aware of Id in rows
Expand Down Expand Up @@ -503,7 +505,7 @@ export const UpdateRole = ({
key={role}
textTransform={"capitalize"}
onClick={() => {
handleRequest(role);
handleRequest(role as UserRole);
}}
>
{role}
Expand Down
5 changes: 0 additions & 5 deletions src/config/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,5 @@ export const ReviewStatus = {
ALL: "all",
} as const;

export const UserRoles = {
REVIEWER: "reviewer",
ADMIN: "admin",
} as const;

export const upstreamOwner = "bitcointranscripts";
export const upstreamRepo = "bitcointranscripts";
27 changes: 27 additions & 0 deletions src/config/permissions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"admin": {
"accessReviews": true,
"accessUsers": true,
"resetReviews": true,
"accessTransactions": true,
"accessAdminNav": true,
"submitToOwnRepo": true
},
"evaluator": {
"accessReviews": true,
"accessUsers": false,
"resetReviews": false,
"accessTransactions": false,
"accessAdminNav": true,
"submitToOwnRepo": false
},
"reviewer": {
"accessReviews": false,
"accessUsers": false,
"resetReviews": false,
"accessTransactions": false,
"accessAdminNav": false,
"submitToOwnRepo": false
}

}
20 changes: 20 additions & 0 deletions src/hooks/useHasPermissions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { useSession } from "next-auth/react";
import { UserRole } from "../../types";
import permissions from "../config/permissions.json";

type Permissions = {
accessReviews: boolean;
accessUsers: boolean;
resetReviews: boolean;
accessTransactions: boolean;
accessAdminNav: boolean;
submitToOwnRepo: boolean;
};

export const useHasPermission = (permission: keyof Permissions) => {
const { data: userSession } = useSession();
const userRole = userSession?.user?.permissions as UserRole;
const userPermissions = permissions[userRole];

return !!userPermissions?.[permission];
};
11 changes: 4 additions & 7 deletions src/pages/admin/reviews.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,8 @@ import {
MenuList,
Text,
} from "@chakra-ui/react";
import { useSession } from "next-auth/react";
import { BiChevronDown } from "react-icons/bi";
import { IoIosFunnel } from "react-icons/io";
import AuthStatus from "@/components/transcript/AuthStatus";
import { useRouter } from "next/router";
import { FilterQueryNames, ReviewStatus } from "@/config/default";
import Pagination from "@/components/tables/Pagination";
Expand All @@ -21,6 +19,8 @@ import { UI_CONFIG } from "@/config/ui-config";
import AdminReviewsTable from "@/components/tables/AdminReviewsTable";
import { useGetAllReviews } from "@/services/api/admin/useReviews";
import { RefetchButton } from "@/components/tables/TableItems";
import { useHasPermission } from "@/hooks/useHasPermissions";
import AuthStatus from "@/components/transcript/AuthStatus";

type OnSelect<T> = (name: string, item: T) => void;

Expand Down Expand Up @@ -79,9 +79,7 @@ const Reviews = () => {
const statusFilter = urlParams.get(FilterQueryNames.status);
const pageQuery = urlParams.get(FilterQueryNames.page);

const { data: sessionData } = useSession();

const isAdmin = sessionData?.user?.permissions === "admin";
const canAccessReviews = useHasPermission("accessReviews");

const {
data: adminReviews,
Expand Down Expand Up @@ -146,15 +144,14 @@ const Reviews = () => {
UI_CONFIG.DEBOUNCE_DELAY
);

if (!isAdmin) {
if (!canAccessReviews) {
return (
<AuthStatus
title="Unauthorized"
message="You are not authorized to access this page"
/>
);
}

return (
<>
<Flex flexDir="column">
Expand Down
Loading

0 comments on commit 1e24d52

Please sign in to comment.