Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use object permissions in object details and object items #4606

Merged
merged 40 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
d443266
remove permission from branch selector
pa-lem Oct 11, 2024
508805d
check permission for object details and items
pa-lem Oct 11, 2024
5cc5d1b
update types
pa-lem Oct 11, 2024
3e3d069
update mock data
pa-lem Oct 11, 2024
c73f615
fix loading state for items
pa-lem Oct 12, 2024
be762a0
update test for role-management (will be improved)
pa-lem Oct 12, 2024
fce0d1b
skip anonymous test for now
pa-lem Oct 12, 2024
7c6a826
use admin auth for artifacts
pa-lem Oct 12, 2024
49edb00
fix artifacts details view with object permission
pa-lem Oct 12, 2024
93223fb
fix filter test
pa-lem Oct 13, 2024
e10ec9d
add kind for filter in use object items for permission
pa-lem Oct 13, 2024
4b7c939
fix auth for object list
pa-lem Oct 13, 2024
171720c
fix auth
pa-lem Oct 13, 2024
8b139b6
skip anonymous profile test
pa-lem Oct 13, 2024
fec8db1
skip profiles
pa-lem Oct 14, 2024
6383f74
use auth in hierarchical view test
pa-lem Oct 14, 2024
677467b
fix search e2e
bilalabbad Oct 14, 2024
1cd5acd
fix token.spec.ts
bilalabbad Oct 14, 2024
c56c0da
fix profile
bilalabbad Oct 14, 2024
bb439f3
permission for profile
bilalabbad Oct 14, 2024
103f4b2
add flag for artifact
pa-lem Oct 14, 2024
cc1a1fc
remove log
pa-lem Oct 14, 2024
0120950
fix unauth test
pa-lem Oct 14, 2024
6639f3a
update permissions structure to not provide a message if it's allowed…
pa-lem Oct 15, 2024
d6bd31d
add message from api on 403
pa-lem Oct 15, 2024
9157a6d
update message
pa-lem Oct 15, 2024
a41f331
update message
pa-lem Oct 15, 2024
83d96cc
start update messages and use new logic with current branch
pa-lem Oct 16, 2024
a4e9dbd
update types + renaming
bilalabbad Oct 16, 2024
e2bfc1e
better message
bilalabbad Oct 16, 2024
69bf48a
disable generic selector item when not allowed to create
bilalabbad Oct 16, 2024
b59efbc
moved to permission folder
bilalabbad Oct 16, 2024
90d054f
enabled skipped tests
bilalabbad Oct 16, 2024
5a8c065
fix mock data
pa-lem Oct 16, 2024
f4587dd
fix number pool
bilalabbad Oct 17, 2024
e79a3ef
fix profiles spec
bilalabbad Oct 17, 2024
681b5fb
fix error causing
bilalabbad Oct 17, 2024
c4f99f2
fix tutorial
bilalabbad Oct 17, 2024
92dfed1
cleaning
bilalabbad Oct 17, 2024
1648dad
cleaning 2
bilalabbad Oct 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions frontend/app/src/components/branch-selector.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { QSP } from "@/config/qsp";
import { Branch } from "@/generated/graphql";
import { usePermission } from "@/hooks/usePermission";
import { branchesState, currentBranchAtom } from "@/state/atoms/branches.atom";
import { branchesToSelectOptions } from "@/utils/branches";
import { Icon } from "@iconify-icon/react";
import { useAtomValue } from "jotai/index";
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { StringParam, useQueryParam } from "use-query-params";

import { ComboboxItem } from "@/components/ui/combobox";
import { Command, CommandEmpty, CommandInput, CommandList } from "@/components/ui/command";
import graphqlClient from "@/graphql/graphqlClientApollo";
import { useAuth } from "@/hooks/useAuth";
import { constructPath } from "@/utils/fetch";
import { useSetAtom } from "jotai";
import { Button, ButtonWithTooltip, LinkButton } from "./buttons/button-primitive";
Expand Down Expand Up @@ -162,13 +162,13 @@ function BranchOption({ branch, onChange }: { branch: Branch; onChange: () => vo
}

export const BranchFormTriggerButton = ({ setOpen }: { setOpen: (open: boolean) => void }) => {
const permission = usePermission();
const { isAuthenticated } = useAuth();

return (
<ButtonWithTooltip
disabled={!permission.write.allow}
tooltipEnabled={!permission.write.allow}
tooltipContent={permission.write.message ?? undefined}
disabled={!isAuthenticated}
tooltipEnabled={!isAuthenticated}
tooltipContent={"You need to be authenticated."}
className="h-8 w-8 shadow-none"
onKeyDown={(e) => {
if (e.key === "Enter") {
Expand Down
4 changes: 2 additions & 2 deletions frontend/app/src/components/form/generic-object-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ interface GenericObjectFormProps extends Omit<ObjectFormProps, "kind"> {
}

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

if (!genericSchema.used_by || genericSchema.used_by?.length === 0) {
Expand Down
75 changes: 68 additions & 7 deletions frontend/app/src/components/form/generic-selector.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,50 @@
import { Combobox, tComboboxItem } from "@/components/ui/combobox-legacy";
import { Badge } from "@/components/ui/badge";
import {
Combobox,
ComboboxContent,
ComboboxEmpty,
ComboboxItem,
ComboboxList,
ComboboxTrigger,
} from "@/components/ui/combobox";
import Label from "@/components/ui/label";
import { PROFILE_KIND } from "@/config/constants";
import useQuery from "@/hooks/useQuery";
import { useSchema } from "@/hooks/useSchema";
import LoadingScreen from "@/screens/loading-screen/loading-screen";
import { getObjectPermissionsQuery } from "@/screens/permission/queries/getObjectPermissions";
import { PermissionData } from "@/screens/permission/types";
import { getPermission } from "@/screens/permission/utils";
import { genericsState, profilesAtom, schemaState } from "@/state/atoms/schema.atom";
import { gql } from "@apollo/client";
import { useAtomValue } from "jotai/index";
import { useId } from "react";
import React, { useId, useState } from "react";

type GenericSelectorProps = {
currentKind: string;
kindInheritingFromGeneric: string[];
value?: string;
onChange: (item: string) => void;
value?: string | null;
onChange: (item: string | null) => void;
};

export const GenericSelector = ({
currentKind,
kindInheritingFromGeneric,
...props
value,
onChange,
}: GenericSelectorProps) => {
const id = useId();
const nodeSchemas = useAtomValue(schemaState);
const nodeGenerics = useAtomValue(genericsState);
const profileSchemas = useAtomValue(profilesAtom);
const { schema } = useSchema(value);
const [open, setOpen] = useState(false);
const { data, loading } = useQuery(gql(getObjectPermissionsQuery(currentKind)));

const items: Array<tComboboxItem> = kindInheritingFromGeneric
if (loading) return <LoadingScreen />;
const permissionsData: Array<{ node: PermissionData }> = data?.[currentKind]?.permissions?.edges;

const items = kindInheritingFromGeneric
.map((usedByKind) => {
const relatedSchema = [...nodeSchemas, ...profileSchemas].find(
(schema) => schema.kind === usedByKind
Expand Down Expand Up @@ -62,7 +84,46 @@ export const GenericSelector = ({
return (
<div className="p-4 bg-gray-200">
<Label htmlFor={id}>Select an object type</Label>
<Combobox id={id} items={items} {...props} />
<Combobox open={open} onOpenChange={setOpen}>
<ComboboxTrigger id={id}>
{schema && <SchemaItem label={schema.label as string} badge={schema.namespace} />}
</ComboboxTrigger>

<ComboboxContent>
<ComboboxList>
<ComboboxEmpty>No schema found.</ComboboxEmpty>
{items.map((item) => {
const itemValue = item?.value as string;
const permissionToCreate = getPermission(
permissionsData.filter(({ node }) => node.kind === itemValue)
).create;

return (
<ComboboxItem
key={itemValue}
value={itemValue}
selectedValue={value}
onSelect={() => {
onChange(value === itemValue ? null : itemValue);
setOpen(false);
}}
disabled={!permissionToCreate.isAllowed}
>
<SchemaItem label={item.label} badge={item.badge} />
</ComboboxItem>
);
})}
</ComboboxList>
</ComboboxContent>
</Combobox>
</div>
);
};

const SchemaItem = ({ label, badge }: { label: string; badge: string }) => {
return (
<div className="flex justify-between w-full">
<span>{label}</span> <Badge>{badge}</Badge>
</div>
);
};
18 changes: 6 additions & 12 deletions frontend/app/src/components/form/object-create-form-trigger.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import SlideOver, { SlideOverTitle } from "@/components/display/slide-over";
import ObjectForm from "@/components/form/object-form";
import { ACCOUNT_GENERIC_OBJECT, ARTIFACT_OBJECT } from "@/config/constants";
import { ARTIFACT_OBJECT } from "@/config/constants";
import graphqlClient from "@/graphql/graphqlClientApollo";
import { usePermission } from "@/hooks/usePermission";
import { Permission } from "@/screens/permission/types";
import { IModelSchema } from "@/state/atoms/schema.atom";
import { isGeneric } from "@/utils/common";
import { Icon } from "@iconify-icon/react";
import { useState } from "react";
import { Button, ButtonProps } from "../buttons/button-primitive";
Expand All @@ -13,32 +12,27 @@ import { Tooltip } from "../ui/tooltip";
interface ObjectCreateFormTriggerProps extends ButtonProps {
schema: IModelSchema;
onSuccess?: (newObject: any) => void;
permission: Permission;
}

export const ObjectCreateFormTrigger = ({
schema,
onSuccess,
isLoading,
permission,
bilalabbad marked this conversation as resolved.
Show resolved Hide resolved
...props
}: ObjectCreateFormTriggerProps) => {
const permission = usePermission();

const [showCreateDrawer, setShowCreateDrawer] = useState(false);

if (schema.kind === ARTIFACT_OBJECT) {
return null;
}

const isAccount: boolean =
schema.kind === ACCOUNT_GENERIC_OBJECT ||
(!isGeneric(schema) && !!schema.inherit_from?.includes(ACCOUNT_GENERIC_OBJECT));

const isAllowed = isAccount ? permission.isAdmin.allow : permission.write.allow;
const tooltipMessage = isAccount ? permission.isAdmin.message : permission.isAdmin.message;
const isAllowed = permission.create.isAllowed;

return (
<>
<Tooltip enabled={!isAllowed} content={tooltipMessage}>
<Tooltip enabled={!isAllowed} content={!isAllowed && permission.create.message}>
<Button
data-cy="create"
data-testid="create-object-button"
Expand Down
2 changes: 1 addition & 1 deletion frontend/app/src/components/time-selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export const TimeFrameSelector = () => {
selected={date}
onChange={onChange}
showTimeSelect
timeIntervals={10}
timeIntervals={1}
calendarStartDay={1}
maxDate={new Date()}
filterTime={(date) => isPast(date)}
Expand Down
4 changes: 4 additions & 0 deletions frontend/app/src/graphql/graphqlClientApollo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ export const errorLink = onError(({ graphQLErrors, operation, forward }) => {
forward(operation);
});
}
case 403: {
// Do not display alert on unauthorized errors
return;
}
default:
const { processErrorMessage } = operation.getContext();

Expand Down
14 changes: 14 additions & 0 deletions frontend/app/src/graphql/queries/objects/getObjectDetails.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,20 @@ query {{kind}} {
{{/if}}
}
}

{{#if hasPermissions}}
permissions {
edges {
node {
kind
view
create
update
delete
}
}
}
{{/if}}
}

{{#if taskKind}}
Expand Down
14 changes: 14 additions & 0 deletions frontend/app/src/graphql/queries/objects/getObjectItems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,20 @@ query {{kind}} (
{{/each}}
}
}

{{#if hasPermissions}}
permissions {
edges {
node {
kind
view
create
update
delete
}
}
}
{{/if}}
}
}
`);
17 changes: 16 additions & 1 deletion frontend/app/src/hooks/useObjectDetails.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { PROFILE_KIND, TASK_OBJECT } from "@/config/constants";
import { getObjectDetailsPaginated } from "@/graphql/queries/objects/getObjectDetails";
import useQuery from "@/hooks/useQuery";
import { getPermission } from "@/screens/permission/utils";
import { IModelSchema, genericsState } from "@/state/atoms/schema.atom";
import { isGeneric } from "@/utils/common";
import { getSchemaObjectColumns, getTabs } from "@/utils/getSchemaObjectColumns";
Expand All @@ -14,6 +15,7 @@ export const useObjectDetails = (schema: IModelSchema, objectId: string) => {
const relationshipsTabs = getTabs(schema);
const columns = getSchemaObjectColumns({ schema });

const isProfileSchema = schema.namespace === "Profile";
const query = gql(
schema
? getObjectDetailsPaginated({
Expand All @@ -28,14 +30,27 @@ export const useObjectDetails = (schema: IModelSchema, objectId: string) => {
schema?.kind !== PROFILE_KIND &&
!isGeneric(schema) &&
schema?.generate_profile,
hasPermissions: !isProfileSchema,
})
: // Empty query to make the gql parsing work
// TODO: Find another solution for queries while loading schema
"query { ok }"
);

return useQuery(query, {
const apolloQuery = useQuery(query, {
skip: !schema,
notifyOnNetworkStatusChange: true,
});

const permissionData =
schema?.kind && apolloQuery?.data?.[schema.kind]?.permissions?.edges
? apolloQuery.data[schema.kind].permissions.edges
: null;

const permission = getPermission(permissionData);

return {
...apolloQuery,
permission,
};
};
32 changes: 30 additions & 2 deletions frontend/app/src/hooks/useObjectItems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { getTokens } from "@/graphql/queries/accounts/getTokens";
import { getObjectItemsPaginated } from "@/graphql/queries/objects/getObjectItems";
import { Filter } from "@/hooks/useFilters";
import useQuery from "@/hooks/useQuery";
import { getPermission } from "@/screens/permission/utils";
import { IModelSchema, genericsState, profilesAtom, schemaState } from "@/state/atoms/schema.atom";
import { getObjectAttributes, getObjectRelationships } from "@/utils/getSchemaObjectColumns";
import { gql } from "@apollo/client";
Expand Down Expand Up @@ -50,18 +51,45 @@ const getQuery = (schema?: IModelSchema, filters?: Array<Filter>) => {

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

const isProfileSchema = schema.namespace === "Profile";

return getObjectItemsPaginated({
kind: kindFilterSchema?.kind || schema.kind,
attributes,
relationships,
filters: filtersString,
hasPermissions: !isProfileSchema,
});
};

export const useObjectItems = (schema?: IModelSchema, filters?: Array<Filter>) => {
export const useObjectItems = (
schema?: IModelSchema,
filters?: Array<Filter>,
kindFilter?: string
) => {
const query = gql`
${getQuery(schema, filters)}
`;

return useQuery(query, { notifyOnNetworkStatusChange: true, skip: !schema });
const apolloQuery = useQuery(query, { notifyOnNetworkStatusChange: true, skip: !schema });

const currentKind = kindFilter || schema?.kind;
const hasPermission = !!(
currentKind &&
apolloQuery?.data &&
apolloQuery?.data[currentKind]?.permissions
);

const permissionData = hasPermission
? apolloQuery.data[currentKind].permissions?.edges
? apolloQuery.data[currentKind].permissions.edges
: apolloQuery.data[currentKind].permissions
: null;

const permission = getPermission(permissionData);

return {
...apolloQuery,
permission,
};
};
2 changes: 1 addition & 1 deletion frontend/app/src/hooks/useSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
} from "@/state/atoms/schema.atom";
import { useAtomValue } from "jotai/index";

type UseSchema = (kind?: string) =>
type UseSchema = (kind?: string | null) =>
| {
schema: iNodeSchema;
isGeneric: false;
Expand Down
Loading
Loading