Skip to content

Commit

Permalink
feat: add view for permission based on resource/team/person
Browse files Browse the repository at this point in the history
Fixes #2246
  • Loading branch information
mainawycliffe committed Sep 23, 2024
1 parent f8de4a8 commit 47f39ac
Show file tree
Hide file tree
Showing 6 changed files with 285 additions and 2 deletions.
75 changes: 75 additions & 0 deletions src/api/services/permissions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { AVATAR_INFO } from "@flanksource-ui/constants";
import { IncidentCommander } from "../axios";
import { resolvePostGrestRequestWithPagination } from "../resolve";
import { PermissionAPIResponse } from "../types/permissions";

export type FetchPermissionsInput = {
componentId?: string;
personId?: string;
teamId?: string;
configId?: string;
checkId?: string;
canaryId?: string;
playbookId?: string;
};

function composeQueryParamForFetchPermissions({
componentId,
personId,
teamId,
configId,
checkId,
canaryId,
playbookId
}: FetchPermissionsInput) {
if (componentId) {
return `component_id=eq.${componentId}`;
}
if (personId) {
return `person_id=eq.${personId}`;
}
if (teamId) {
return `team_id=eq.${teamId}`;
}
if (configId) {
return `config_id=eq.${configId}`;
}
if (checkId) {
return `check_id=eq.${checkId}`;
}
if (canaryId) {
return `canary_id=eq.${canaryId}`;
}
if (playbookId) {
return `playbook_id=eq.${playbookId}`;
}
return undefined;
}

export function fetchPermissions(
input: FetchPermissionsInput,
pagination: {
pageSize: number;
pageIndex: number;
}
) {
const queryParam = composeQueryParamForFetchPermissions(input);
const selectFields = [
"*",
"checks:check_id(id, name, status, type)",
"catalog:config_id(id, name, type, config_class)",
"component:component_id(id, name, icon)",
"canary:canary_id(id, name, icon)",
"playbook:playbook_id(id, title, name, icon)",
"team:team_id(id, name, icon)",
`person:person_id(${AVATAR_INFO})`,
`createdBy:created_by(${AVATAR_INFO})`
];

const { pageSize, pageIndex } = pagination;

const url = `/permissions?${queryParam}&select=${selectFields.join(",")}&limit=${pageSize}&offset=${pageIndex * pageSize}`;
return resolvePostGrestRequestWithPagination(
IncidentCommander.get<PermissionAPIResponse[]>(url)
);
}
34 changes: 34 additions & 0 deletions src/api/types/permissions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { ConfigItem } from "./configs";
import { HealthCheck } from "./health";
import { PlaybookSpec } from "./playbooks";
import { Topology } from "./topology";
import { Team, User } from "./users";

export type PermissionTable = {
id: string;
description: string;
action: string;
deny?: boolean;
component_id?: string;
config_id?: string;
canary_id?: string;
playbook_id?: string;
created_by: string;
person_id?: string;
team_id?: string;
updated_by: string;
created_at: string;
updated_at: string;
until?: string;
};

export type PermissionAPIResponse = PermissionTable & {
checks: Pick<HealthCheck, "id" | "name" | "type" | "status">;
catalog: Pick<ConfigItem, "id" | "name" | "type" | "config_class">;
component: Pick<Topology, "id" | "name" | "icon">;
canary: Pick<Topology, "id" | "name" | "icon">;
playbook: Pick<PlaybookSpec, "id" | "name" | "icon" | "title">;
team: Pick<Team, "id" | "name" | "icon">;
person: User;
createdBy: User;
};
24 changes: 24 additions & 0 deletions src/components/Canary/CanaryLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Icon } from "@flanksource-ui/ui/Icons/Icon";
import { Link } from "react-router-dom";

type CanaryLinkProps = {
canary: {
id: string;
name: string;
icon?: string;
};
};

export default function CanaryLink({ canary }: CanaryLinkProps) {
return (
<Link
className="flex flex-row items-center gap-1"
to={{
pathname: `/settings/canaries/${canary.id}`
}}
>
{canary.icon && <Icon name={canary.icon} className="h-5" />}
<span>{canary.name}</span>
</Link>
);
}
94 changes: 94 additions & 0 deletions src/components/Permissions/PermissionsTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { PermissionAPIResponse } from "@flanksource-ui/api/types/permissions";
import { Avatar } from "@flanksource-ui/ui/Avatar";
import { Badge } from "@flanksource-ui/ui/Badge/Badge";
import { MRTDateCell } from "@flanksource-ui/ui/MRTDataTable/Cells/MRTDateCells";
import MRTDataTable from "@flanksource-ui/ui/MRTDataTable/MRTDataTable";
import { MRT_ColumnDef } from "mantine-react-table";
import CanaryLink from "../Canary/CanaryLink";
import { CheckLink } from "../Canary/HealthChecks/CheckLink";
import ConfigLink from "../Configs/ConfigLink/ConfigLink";
import PlaybookSpecIcon from "../Playbooks/Settings/PlaybookSpecIcon";
import { TopologyLink } from "../Topology/TopologyLink";

const permissionsTableColumns: MRT_ColumnDef<PermissionAPIResponse>[] = [
{
id: "Resource",
header: "Resource",
Cell: ({ row }) => {
const config = row.original.catalog;
const check = row.original.checks;
const playbook = row.original.playbook;
const canary = row.original.canary;
const component = row.original.component;

return (
<div className="flex flex-col">
{config && <ConfigLink config={config} />}
{check && <CheckLink check={check} />}
{playbook && <PlaybookSpecIcon playbook={playbook} showLabel />}
{canary && <CanaryLink canary={canary} />}
{component && <TopologyLink topology={component} />}
</div>
);
}
},
{
id: "action",
header: "Action",
Cell: ({ row }) => {
const action = row.original.action;
const deny = row.original.deny;

return (
<div>
<span>{action}</span>
{deny && <Badge text="deny" />}
</div>
);
}
},
{
id: "updated",
header: "Updated",
accessorFn: (row) => row.updated_at,
Cell: MRTDateCell
},
{
id: "created",
header: "Created",
accessorFn: (row) => row.created_at
},
{
id: "createdBy",
header: "Created By",
Cell: ({ row }) => {
const createdBy = row.original.createdBy;
return <Avatar user={createdBy} />;
}
}
];

type PermissionsTableProps = {
permissions: PermissionAPIResponse[];
isLoading: boolean;
pageCount: number;
totalEntries: number;
};

export default function PermissionsTable({
permissions,
isLoading,
pageCount,
totalEntries
}: PermissionsTableProps) {
return (
<MRTDataTable<PermissionAPIResponse>
columns={permissionsTableColumns}
data={permissions}
isLoading={isLoading}
manualPageCount={pageCount}
totalRowCount={totalEntries}
enableServerSidePagination
/>
);
}
54 changes: 54 additions & 0 deletions src/components/Permissions/PermissionsView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {
fetchPermissions,
FetchPermissionsInput
} from "@flanksource-ui/api/services/permissions";
import useReactTablePaginationState from "@flanksource-ui/ui/DataTable/Hooks/useReactTablePaginationState";
import { useQuery } from "@tanstack/react-query";
import { useMemo } from "react";
import PermissionsTable from "./PermissionsTable";

type PermissionsViewProps = {
permissionRequest: FetchPermissionsInput;
};

export default function PermissionsView({
permissionRequest
}: PermissionsViewProps) {
const { pageSize, pageIndex } = useReactTablePaginationState();

const isEnabled = useMemo(() => {
return Object.values(permissionRequest).some(
(value) => value !== undefined
);
}, [permissionRequest]);

const { isLoading, data } = useQuery({
queryKey: [
"permissions",
permissionRequest,
{
pageIndex,
pageSize
}
],
queryFn: () =>
fetchPermissions(permissionRequest, {
pageIndex,
pageSize
}),
enabled: isEnabled
});

const totalEntries = data?.totalEntries || 0;
const pageCount = totalEntries ? Math.ceil(totalEntries / pageSize) : -1;
const permissions = data?.data || [];

return (
<PermissionsTable
permissions={permissions}
isLoading={isLoading}
pageCount={pageCount}
totalEntries={totalEntries}
/>
);
}
6 changes: 4 additions & 2 deletions src/ui/Badge/Badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ type BadgeProps = {
text: React.ReactNode;
value?: string;
size?: "xs" | "sm" | "md";
color?: "blue" | "gray";
color?: "blue" | "gray" | "yellow";
dot?: string;
title?: string;
className?: string;
Expand All @@ -29,7 +29,9 @@ export function Badge({
const colorClass =
color === "blue"
? "bg-blue-100 text-blue-800"
: "bg-gray-100 text-gray-700";
: color === "yellow"
? "bg-yellow-100 text-yellow-800"
: "bg-gray-100 text-gray-700";
const spanClassName =
size === "sm" ? "text-sm px-1 py-0.5" : "text-xs px-1 py-0.5";
const svgClassName =
Expand Down

0 comments on commit 47f39ac

Please sign in to comment.