Skip to content

Commit cb5f267

Browse files
pa-lembilalabbad
andauthored
Use object permissions in object details and object items (#4606)
* remove permission from branch selector * check permission for object details and items * update types * update mock data * fix loading state for items * update test for role-management (will be improved) * skip anonymous test for now * use admin auth for artifacts * fix artifacts details view with object permission * fix filter test * add kind for filter in use object items for permission * fix auth for object list * fix auth * skip anonymous profile test * skip profiles * use auth in hierarchical view test * fix search e2e * fix token.spec.ts * fix profile * permission for profile * add flag for artifact * remove log * fix unauth test * update permissions structure to not provide a message if it's allowed + relationship example * add message from api on 403 * update message * update message * start update messages and use new logic with current branch * update types + renaming * better message * disable generic selector item when not allowed to create * moved to permission folder * enabled skipped tests * fix mock data * fix number pool * fix profiles spec * fix error causing * fix tutorial * cleaning * cleaning 2 --------- Co-authored-by: bilalabbad <[email protected]>
1 parent 852a6d8 commit cb5f267

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+621
-149
lines changed

frontend/app/src/components/branch-selector.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
22
import { QSP } from "@/config/qsp";
33
import { Branch } from "@/generated/graphql";
4-
import { usePermission } from "@/hooks/usePermission";
54
import { branchesState, currentBranchAtom } from "@/state/atoms/branches.atom";
65
import { branchesToSelectOptions } from "@/utils/branches";
76
import { Icon } from "@iconify-icon/react";
@@ -12,6 +11,7 @@ import { StringParam, useQueryParam } from "use-query-params";
1211
import { ComboboxItem } from "@/components/ui/combobox";
1312
import { Command, CommandEmpty, CommandInput, CommandList } from "@/components/ui/command";
1413
import graphqlClient from "@/graphql/graphqlClientApollo";
14+
import { useAuth } from "@/hooks/useAuth";
1515
import { constructPath } from "@/utils/fetch";
1616
import { useSetAtom } from "jotai";
1717
import { Button, ButtonWithTooltip, LinkButton } from "./buttons/button-primitive";
@@ -162,13 +162,13 @@ function BranchOption({ branch, onChange }: { branch: Branch; onChange: () => vo
162162
}
163163

164164
export const BranchFormTriggerButton = ({ setOpen }: { setOpen: (open: boolean) => void }) => {
165-
const permission = usePermission();
165+
const { isAuthenticated } = useAuth();
166166

167167
return (
168168
<ButtonWithTooltip
169-
disabled={!permission.write.allow}
170-
tooltipEnabled={!permission.write.allow}
171-
tooltipContent={permission.write.message ?? undefined}
169+
disabled={!isAuthenticated}
170+
tooltipEnabled={!isAuthenticated}
171+
tooltipContent={"You need to be authenticated."}
172172
className="h-8 w-8 shadow-none"
173173
onKeyDown={(e) => {
174174
if (e.key === "Enter") {

frontend/app/src/components/form/generic-object-form.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ interface GenericObjectFormProps extends Omit<ObjectFormProps, "kind"> {
99
}
1010

1111
export const GenericObjectForm = ({ genericSchema, ...props }: GenericObjectFormProps) => {
12-
const [kindToCreate, setKindToCreate] = useState<string | undefined>(
13-
genericSchema.used_by?.length === 1 ? genericSchema.used_by[0] : undefined
12+
const [kindToCreate, setKindToCreate] = useState<string | null>(
13+
genericSchema.used_by?.length === 1 ? genericSchema.used_by[0] : null
1414
);
1515

1616
if (!genericSchema.used_by || genericSchema.used_by?.length === 0) {

frontend/app/src/components/form/generic-selector.tsx

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,50 @@
1-
import { Combobox, tComboboxItem } from "@/components/ui/combobox-legacy";
1+
import { Badge } from "@/components/ui/badge";
2+
import {
3+
Combobox,
4+
ComboboxContent,
5+
ComboboxEmpty,
6+
ComboboxItem,
7+
ComboboxList,
8+
ComboboxTrigger,
9+
} from "@/components/ui/combobox";
210
import Label from "@/components/ui/label";
311
import { PROFILE_KIND } from "@/config/constants";
12+
import useQuery from "@/hooks/useQuery";
13+
import { useSchema } from "@/hooks/useSchema";
14+
import LoadingScreen from "@/screens/loading-screen/loading-screen";
15+
import { getObjectPermissionsQuery } from "@/screens/permission/queries/getObjectPermissions";
16+
import { PermissionData } from "@/screens/permission/types";
17+
import { getPermission } from "@/screens/permission/utils";
418
import { genericsState, profilesAtom, schemaState } from "@/state/atoms/schema.atom";
19+
import { gql } from "@apollo/client";
520
import { useAtomValue } from "jotai/index";
6-
import { useId } from "react";
21+
import React, { useId, useState } from "react";
722

823
type GenericSelectorProps = {
924
currentKind: string;
1025
kindInheritingFromGeneric: string[];
11-
value?: string;
12-
onChange: (item: string) => void;
26+
value?: string | null;
27+
onChange: (item: string | null) => void;
1328
};
1429

1530
export const GenericSelector = ({
1631
currentKind,
1732
kindInheritingFromGeneric,
18-
...props
33+
value,
34+
onChange,
1935
}: GenericSelectorProps) => {
2036
const id = useId();
2137
const nodeSchemas = useAtomValue(schemaState);
2238
const nodeGenerics = useAtomValue(genericsState);
2339
const profileSchemas = useAtomValue(profilesAtom);
40+
const { schema } = useSchema(value);
41+
const [open, setOpen] = useState(false);
42+
const { data, loading } = useQuery(gql(getObjectPermissionsQuery(currentKind)));
2443

25-
const items: Array<tComboboxItem> = kindInheritingFromGeneric
44+
if (loading) return <LoadingScreen />;
45+
const permissionsData: Array<{ node: PermissionData }> = data?.[currentKind]?.permissions?.edges;
46+
47+
const items = kindInheritingFromGeneric
2648
.map((usedByKind) => {
2749
const relatedSchema = [...nodeSchemas, ...profileSchemas].find(
2850
(schema) => schema.kind === usedByKind
@@ -62,7 +84,46 @@ export const GenericSelector = ({
6284
return (
6385
<div className="p-4 bg-gray-200">
6486
<Label htmlFor={id}>Select an object type</Label>
65-
<Combobox id={id} items={items} {...props} />
87+
<Combobox open={open} onOpenChange={setOpen}>
88+
<ComboboxTrigger id={id}>
89+
{schema && <SchemaItem label={schema.label as string} badge={schema.namespace} />}
90+
</ComboboxTrigger>
91+
92+
<ComboboxContent>
93+
<ComboboxList>
94+
<ComboboxEmpty>No schema found.</ComboboxEmpty>
95+
{items.map((item) => {
96+
const itemValue = item?.value as string;
97+
const permissionToCreate = getPermission(
98+
permissionsData.filter(({ node }) => node.kind === itemValue)
99+
).create;
100+
101+
return (
102+
<ComboboxItem
103+
key={itemValue}
104+
value={itemValue}
105+
selectedValue={value}
106+
onSelect={() => {
107+
onChange(value === itemValue ? null : itemValue);
108+
setOpen(false);
109+
}}
110+
disabled={!permissionToCreate.isAllowed}
111+
>
112+
<SchemaItem label={item.label} badge={item.badge} />
113+
</ComboboxItem>
114+
);
115+
})}
116+
</ComboboxList>
117+
</ComboboxContent>
118+
</Combobox>
119+
</div>
120+
);
121+
};
122+
123+
const SchemaItem = ({ label, badge }: { label: string; badge: string }) => {
124+
return (
125+
<div className="flex justify-between w-full">
126+
<span>{label}</span> <Badge>{badge}</Badge>
66127
</div>
67128
);
68129
};

frontend/app/src/components/form/object-create-form-trigger.tsx

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import SlideOver, { SlideOverTitle } from "@/components/display/slide-over";
22
import ObjectForm from "@/components/form/object-form";
3-
import { ACCOUNT_GENERIC_OBJECT, ARTIFACT_OBJECT } from "@/config/constants";
3+
import { ARTIFACT_OBJECT } from "@/config/constants";
44
import graphqlClient from "@/graphql/graphqlClientApollo";
5-
import { usePermission } from "@/hooks/usePermission";
5+
import { Permission } from "@/screens/permission/types";
66
import { IModelSchema } from "@/state/atoms/schema.atom";
7-
import { isGeneric } from "@/utils/common";
87
import { Icon } from "@iconify-icon/react";
98
import { useState } from "react";
109
import { Button, ButtonProps } from "../buttons/button-primitive";
@@ -13,32 +12,27 @@ import { Tooltip } from "../ui/tooltip";
1312
interface ObjectCreateFormTriggerProps extends ButtonProps {
1413
schema: IModelSchema;
1514
onSuccess?: (newObject: any) => void;
15+
permission: Permission;
1616
}
1717

1818
export const ObjectCreateFormTrigger = ({
1919
schema,
2020
onSuccess,
2121
isLoading,
22+
permission,
2223
...props
2324
}: ObjectCreateFormTriggerProps) => {
24-
const permission = usePermission();
25-
2625
const [showCreateDrawer, setShowCreateDrawer] = useState(false);
2726

2827
if (schema.kind === ARTIFACT_OBJECT) {
2928
return null;
3029
}
3130

32-
const isAccount: boolean =
33-
schema.kind === ACCOUNT_GENERIC_OBJECT ||
34-
(!isGeneric(schema) && !!schema.inherit_from?.includes(ACCOUNT_GENERIC_OBJECT));
35-
36-
const isAllowed = isAccount ? permission.isAdmin.allow : permission.write.allow;
37-
const tooltipMessage = isAccount ? permission.isAdmin.message : permission.isAdmin.message;
31+
const isAllowed = permission.create.isAllowed;
3832

3933
return (
4034
<>
41-
<Tooltip enabled={!isAllowed} content={tooltipMessage}>
35+
<Tooltip enabled={!isAllowed} content={!isAllowed && permission.create.message}>
4236
<Button
4337
data-cy="create"
4438
data-testid="create-object-button"

frontend/app/src/components/time-selector.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export const TimeFrameSelector = () => {
5353
selected={date}
5454
onChange={onChange}
5555
showTimeSelect
56-
timeIntervals={10}
56+
timeIntervals={1}
5757
calendarStartDay={1}
5858
maxDate={new Date()}
5959
filterTime={(date) => isPast(date)}

frontend/app/src/graphql/graphqlClientApollo.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,10 @@ export const errorLink = onError(({ graphQLErrors, operation, forward }) => {
124124
forward(operation);
125125
});
126126
}
127+
case 403: {
128+
// Do not display alert on unauthorized errors
129+
return;
130+
}
127131
default:
128132
const { processErrorMessage } = operation.getContext();
129133

frontend/app/src/graphql/queries/objects/getObjectDetails.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,20 @@ query {{kind}} {
101101
{{/if}}
102102
}
103103
}
104+
105+
{{#if hasPermissions}}
106+
permissions {
107+
edges {
108+
node {
109+
kind
110+
view
111+
create
112+
update
113+
delete
114+
}
115+
}
116+
}
117+
{{/if}}
104118
}
105119
106120
{{#if taskKind}}

frontend/app/src/graphql/queries/objects/getObjectItems.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,20 @@ query {{kind}} (
4545
{{/each}}
4646
}
4747
}
48+
49+
{{#if hasPermissions}}
50+
permissions {
51+
edges {
52+
node {
53+
kind
54+
view
55+
create
56+
update
57+
delete
58+
}
59+
}
60+
}
61+
{{/if}}
4862
}
4963
}
5064
`);

frontend/app/src/hooks/useObjectDetails.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { PROFILE_KIND, TASK_OBJECT } from "@/config/constants";
22
import { getObjectDetailsPaginated } from "@/graphql/queries/objects/getObjectDetails";
33
import useQuery from "@/hooks/useQuery";
4+
import { getPermission } from "@/screens/permission/utils";
45
import { IModelSchema, genericsState } from "@/state/atoms/schema.atom";
56
import { isGeneric } from "@/utils/common";
67
import { getSchemaObjectColumns, getTabs } from "@/utils/getSchemaObjectColumns";
@@ -14,6 +15,7 @@ export const useObjectDetails = (schema: IModelSchema, objectId: string) => {
1415
const relationshipsTabs = getTabs(schema);
1516
const columns = getSchemaObjectColumns({ schema });
1617

18+
const isProfileSchema = schema.namespace === "Profile";
1719
const query = gql(
1820
schema
1921
? getObjectDetailsPaginated({
@@ -28,14 +30,27 @@ export const useObjectDetails = (schema: IModelSchema, objectId: string) => {
2830
schema?.kind !== PROFILE_KIND &&
2931
!isGeneric(schema) &&
3032
schema?.generate_profile,
33+
hasPermissions: !isProfileSchema,
3134
})
3235
: // Empty query to make the gql parsing work
3336
// TODO: Find another solution for queries while loading schema
3437
"query { ok }"
3538
);
3639

37-
return useQuery(query, {
40+
const apolloQuery = useQuery(query, {
3841
skip: !schema,
3942
notifyOnNetworkStatusChange: true,
4043
});
44+
45+
const permissionData =
46+
schema?.kind && apolloQuery?.data?.[schema.kind]?.permissions?.edges
47+
? apolloQuery.data[schema.kind].permissions.edges
48+
: null;
49+
50+
const permission = getPermission(permissionData);
51+
52+
return {
53+
...apolloQuery,
54+
permission,
55+
};
4156
};

frontend/app/src/hooks/useObjectItems.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { getTokens } from "@/graphql/queries/accounts/getTokens";
33
import { getObjectItemsPaginated } from "@/graphql/queries/objects/getObjectItems";
44
import { Filter } from "@/hooks/useFilters";
55
import useQuery from "@/hooks/useQuery";
6+
import { getPermission } from "@/screens/permission/utils";
67
import { IModelSchema, genericsState, profilesAtom, schemaState } from "@/state/atoms/schema.atom";
78
import { getObjectAttributes, getObjectRelationships } from "@/utils/getSchemaObjectColumns";
89
import { gql } from "@apollo/client";
@@ -50,18 +51,45 @@ const getQuery = (schema?: IModelSchema, filters?: Array<Filter>) => {
5051

5152
const relationships = getObjectRelationships({ schema, forListView: true });
5253

54+
const isProfileSchema = schema.namespace === "Profile";
55+
5356
return getObjectItemsPaginated({
5457
kind: kindFilterSchema?.kind || schema.kind,
5558
attributes,
5659
relationships,
5760
filters: filtersString,
61+
hasPermissions: !isProfileSchema,
5862
});
5963
};
6064

61-
export const useObjectItems = (schema?: IModelSchema, filters?: Array<Filter>) => {
65+
export const useObjectItems = (
66+
schema?: IModelSchema,
67+
filters?: Array<Filter>,
68+
kindFilter?: string
69+
) => {
6270
const query = gql`
6371
${getQuery(schema, filters)}
6472
`;
6573

66-
return useQuery(query, { notifyOnNetworkStatusChange: true, skip: !schema });
74+
const apolloQuery = useQuery(query, { notifyOnNetworkStatusChange: true, skip: !schema });
75+
76+
const currentKind = kindFilter || schema?.kind;
77+
const hasPermission = !!(
78+
currentKind &&
79+
apolloQuery?.data &&
80+
apolloQuery?.data[currentKind]?.permissions
81+
);
82+
83+
const permissionData = hasPermission
84+
? apolloQuery.data[currentKind].permissions?.edges
85+
? apolloQuery.data[currentKind].permissions.edges
86+
: apolloQuery.data[currentKind].permissions
87+
: null;
88+
89+
const permission = getPermission(permissionData);
90+
91+
return {
92+
...apolloQuery,
93+
permission,
94+
};
6795
};

0 commit comments

Comments
 (0)