From f3e23850ff0c6a5c9f3b9222da951b0b9f2597d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Wed, 31 Dec 2025 16:31:33 +0100 Subject: [PATCH] Fix data pages permissions handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Émile Ré --- .../core/DataListQuery.graphql.ts | 34 +++++- .../core/DataPageFragment.graphql.ts | 32 +++++- .../core/DatumGraphCreateMutation.graphql.ts | 36 +++++- .../core/DatumGraphListQuery.graphql.ts | 82 +++++++++---- .../core/DatumGraphNodeQuery.graphql.ts | 44 ++++++- apps/console/src/hooks/graph/DatumGraph.ts | 13 ++- .../src/pages/organizations/data/DataPage.tsx | 17 +-- .../organizations/data/DatumDetailsPage.tsx | 17 ++- pkg/server/api/console/v1/schema.graphql | 2 + pkg/server/api/console/v1/schema/schema.go | 108 ++++++++++++++++++ pkg/server/api/console/v1/types/datum.go | 3 + pkg/server/api/console/v1/v1_resolver.go | 5 + 12 files changed, 333 insertions(+), 60 deletions(-) diff --git a/apps/console/src/__generated__/core/DataListQuery.graphql.ts b/apps/console/src/__generated__/core/DataListQuery.graphql.ts index 4e9ed4661..a6ffcdc2d 100644 --- a/apps/console/src/__generated__/core/DataListQuery.graphql.ts +++ b/apps/console/src/__generated__/core/DataListQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<2000fd2ccfcf4b832c51ff71b0326fef>> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -322,6 +322,32 @@ return { "name": "createdAt", "storageKey": null }, + { + "alias": "canUpdate", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "core:datum:update" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"core:datum:update\")" + }, + { + "alias": "canDelete", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "core:datum:delete" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"core:datum:delete\")" + }, (v13/*: any*/) ], "storageKey": null @@ -411,16 +437,16 @@ return { ] }, "params": { - "cacheID": "dbca2ff147e8b1e367a266fcf016b184", + "cacheID": "c022cb17d3ede74899755b606ce95ba9", "id": null, "metadata": {}, "name": "DataListQuery", "operationKind": "query", - "text": "query DataListQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 10\n $last: Int = null\n $order: DatumOrder = null\n $snapshotId: ID = null\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...DataPageFragment_25MC8O\n id\n }\n}\n\nfragment DataPageFragment_25MC8O on Organization {\n data(first: $first, after: $after, last: $last, before: $before, orderBy: $order, filter: {snapshotId: $snapshotId}) {\n edges {\n node {\n id\n name\n dataClassification\n owner {\n fullName\n id\n }\n vendors(first: 50) {\n edges {\n node {\n id\n name\n websiteUrl\n }\n }\n }\n createdAt\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n" + "text": "query DataListQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 10\n $last: Int = null\n $order: DatumOrder = null\n $snapshotId: ID = null\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...DataPageFragment_25MC8O\n id\n }\n}\n\nfragment DataPageFragment_25MC8O on Organization {\n data(first: $first, after: $after, last: $last, before: $before, orderBy: $order, filter: {snapshotId: $snapshotId}) {\n edges {\n node {\n id\n name\n dataClassification\n owner {\n fullName\n id\n }\n vendors(first: 50) {\n edges {\n node {\n id\n name\n websiteUrl\n }\n }\n }\n createdAt\n canUpdate: permission(action: \"core:datum:update\")\n canDelete: permission(action: \"core:datum:delete\")\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n" } }; })(); -(node as any).hash = "5d3069a0d856613ccb854033b5b1f14e"; +(node as any).hash = "9a4453365a733d298ab9f5237fc4153b"; export default node; diff --git a/apps/console/src/__generated__/core/DataPageFragment.graphql.ts b/apps/console/src/__generated__/core/DataPageFragment.graphql.ts index 1f5c8f857..e57385643 100644 --- a/apps/console/src/__generated__/core/DataPageFragment.graphql.ts +++ b/apps/console/src/__generated__/core/DataPageFragment.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<9619ee8f74aea67be931f8c40a8815c8>> + * @generated SignedSource<<213dd4c7bd0e87ba0e64fa6ce8d2c37b>> * @lightSyntaxTransform * @nogrep */ @@ -16,6 +16,8 @@ export type DataPageFragment$data = { readonly __id: string; readonly edges: ReadonlyArray<{ readonly node: { + readonly canDelete: boolean; + readonly canUpdate: boolean; readonly createdAt: any; readonly dataClassification: DataClassification; readonly id: string; @@ -248,6 +250,32 @@ return { "name": "createdAt", "storageKey": null }, + { + "alias": "canUpdate", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "core:datum:update" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"core:datum:update\")" + }, + { + "alias": "canDelete", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "core:datum:delete" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"core:datum:delete\")" + }, { "alias": null, "args": null, @@ -329,6 +357,6 @@ return { }; })(); -(node as any).hash = "5d3069a0d856613ccb854033b5b1f14e"; +(node as any).hash = "9a4453365a733d298ab9f5237fc4153b"; export default node; diff --git a/apps/console/src/__generated__/core/DatumGraphCreateMutation.graphql.ts b/apps/console/src/__generated__/core/DatumGraphCreateMutation.graphql.ts index a4006bd45..5fcf9addf 100644 --- a/apps/console/src/__generated__/core/DatumGraphCreateMutation.graphql.ts +++ b/apps/console/src/__generated__/core/DatumGraphCreateMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<43b67fe6155860e492f2a36543727d49>> + * @generated SignedSource<<8106bb4a10940caebf1f523fb6136350>> * @lightSyntaxTransform * @nogrep */ @@ -25,6 +25,8 @@ export type DatumGraphCreateMutation$data = { readonly createDatum: { readonly datumEdge: { readonly node: { + readonly canDelete: boolean; + readonly canUpdate: boolean; readonly createdAt: any; readonly dataClassification: DataClassification; readonly id: string; @@ -181,6 +183,32 @@ v5 = { "kind": "ScalarField", "name": "createdAt", "storageKey": null + }, + { + "alias": "canUpdate", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "core:datum:update" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"core:datum:update\")" + }, + { + "alias": "canDelete", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "core:datum:delete" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"core:datum:delete\")" } ], "storageKey": null @@ -254,16 +282,16 @@ return { ] }, "params": { - "cacheID": "23ff315b0cc090bcaca7739d945d8d1b", + "cacheID": "37d345acfe37d5edf653d021f8aad43c", "id": null, "metadata": {}, "name": "DatumGraphCreateMutation", "operationKind": "mutation", - "text": "mutation DatumGraphCreateMutation(\n $input: CreateDatumInput!\n) {\n createDatum(input: $input) {\n datumEdge {\n node {\n id\n name\n dataClassification\n owner {\n id\n fullName\n }\n vendors(first: 50) {\n edges {\n node {\n id\n name\n websiteUrl\n }\n }\n }\n createdAt\n }\n }\n }\n}\n" + "text": "mutation DatumGraphCreateMutation(\n $input: CreateDatumInput!\n) {\n createDatum(input: $input) {\n datumEdge {\n node {\n id\n name\n dataClassification\n owner {\n id\n fullName\n }\n vendors(first: 50) {\n edges {\n node {\n id\n name\n websiteUrl\n }\n }\n }\n createdAt\n canUpdate: permission(action: \"core:datum:update\")\n canDelete: permission(action: \"core:datum:delete\")\n }\n }\n }\n}\n" } }; })(); -(node as any).hash = "b148ef55d18d3a45c04b5072581f2285"; +(node as any).hash = "db4781176e1b58224619f8237af92716"; export default node; diff --git a/apps/console/src/__generated__/core/DatumGraphListQuery.graphql.ts b/apps/console/src/__generated__/core/DatumGraphListQuery.graphql.ts index 8085074c2..001cc6d57 100644 --- a/apps/console/src/__generated__/core/DatumGraphListQuery.graphql.ts +++ b/apps/console/src/__generated__/core/DatumGraphListQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<7e67f2a3d41fbea46b7b8447595302a5>> + * @generated SignedSource<<034a68d54259fb22cf2d563699ad4257>> * @lightSyntaxTransform * @nogrep */ @@ -16,6 +16,7 @@ export type DatumGraphListQuery$variables = { }; export type DatumGraphListQuery$data = { readonly node: { + readonly canCreateDatum?: boolean; readonly " $fragmentSpreads": FragmentRefs<"DataPageFragment">; }; }; @@ -44,30 +45,43 @@ v1 = [ "variableName": "organizationId" } ], -v2 = [ +v2 = { + "alias": "canCreateDatum", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "core:datum:create" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"core:datum:create\")" +}, +v3 = [ { "kind": "Variable", "name": "snapshotId", "variableName": "snapshotId" } ], -v3 = { +v4 = { "alias": null, "args": null, "kind": "ScalarField", "name": "__typename", "storageKey": null }, -v4 = { +v5 = { "alias": null, "args": null, "kind": "ScalarField", "name": "id", "storageKey": null }, -v5 = [ +v6 = [ { - "fields": (v2/*: any*/), + "fields": (v3/*: any*/), "kind": "ObjectValue", "name": "filter" }, @@ -77,7 +91,7 @@ v5 = [ "value": 10 } ], -v6 = { +v7 = { "alias": null, "args": null, "kind": "ScalarField", @@ -102,8 +116,9 @@ return { { "kind": "InlineFragment", "selections": [ + (v2/*: any*/), { - "args": (v2/*: any*/), + "args": (v3/*: any*/), "kind": "FragmentSpread", "name": "DataPageFragment" } @@ -132,14 +147,15 @@ return { "name": "node", "plural": false, "selections": [ - (v3/*: any*/), (v4/*: any*/), + (v5/*: any*/), { "kind": "InlineFragment", "selections": [ + (v2/*: any*/), { "alias": null, - "args": (v5/*: any*/), + "args": (v6/*: any*/), "concreteType": "DatumConnection", "kind": "LinkedField", "name": "data", @@ -161,8 +177,8 @@ return { "name": "node", "plural": false, "selections": [ - (v4/*: any*/), - (v6/*: any*/), + (v5/*: any*/), + (v7/*: any*/), { "alias": null, "args": null, @@ -185,7 +201,7 @@ return { "name": "fullName", "storageKey": null }, - (v4/*: any*/) + (v5/*: any*/) ], "storageKey": null }, @@ -219,8 +235,8 @@ return { "name": "node", "plural": false, "selections": [ - (v4/*: any*/), - (v6/*: any*/), + (v5/*: any*/), + (v7/*: any*/), { "alias": null, "args": null, @@ -244,7 +260,33 @@ return { "name": "createdAt", "storageKey": null }, - (v3/*: any*/) + { + "alias": "canUpdate", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "core:datum:update" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"core:datum:update\")" + }, + { + "alias": "canDelete", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "core:datum:delete" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"core:datum:delete\")" + }, + (v4/*: any*/) ], "storageKey": null }, @@ -314,7 +356,7 @@ return { }, { "alias": null, - "args": (v5/*: any*/), + "args": (v6/*: any*/), "filters": [ "filter" ], @@ -333,16 +375,16 @@ return { ] }, "params": { - "cacheID": "7200b7cf37859346d740de3f96f1d443", + "cacheID": "203f6661d76c730336cffaa96b631ab8", "id": null, "metadata": {}, "name": "DatumGraphListQuery", "operationKind": "query", - "text": "query DatumGraphListQuery(\n $organizationId: ID!\n $snapshotId: ID = null\n) {\n node(id: $organizationId) {\n __typename\n ... on Organization {\n ...DataPageFragment_3iomuz\n }\n id\n }\n}\n\nfragment DataPageFragment_3iomuz on Organization {\n data(first: 10, filter: {snapshotId: $snapshotId}) {\n edges {\n node {\n id\n name\n dataClassification\n owner {\n fullName\n id\n }\n vendors(first: 50) {\n edges {\n node {\n id\n name\n websiteUrl\n }\n }\n }\n createdAt\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n" + "text": "query DatumGraphListQuery(\n $organizationId: ID!\n $snapshotId: ID = null\n) {\n node(id: $organizationId) {\n __typename\n ... on Organization {\n canCreateDatum: permission(action: \"core:datum:create\")\n ...DataPageFragment_3iomuz\n }\n id\n }\n}\n\nfragment DataPageFragment_3iomuz on Organization {\n data(first: 10, filter: {snapshotId: $snapshotId}) {\n edges {\n node {\n id\n name\n dataClassification\n owner {\n fullName\n id\n }\n vendors(first: 50) {\n edges {\n node {\n id\n name\n websiteUrl\n }\n }\n }\n createdAt\n canUpdate: permission(action: \"core:datum:update\")\n canDelete: permission(action: \"core:datum:delete\")\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n" } }; })(); -(node as any).hash = "e0782332e7e82bd1a5e7f5e40ada2dae"; +(node as any).hash = "e071e19eda0d4c105668ffd9671399bd"; export default node; diff --git a/apps/console/src/__generated__/core/DatumGraphNodeQuery.graphql.ts b/apps/console/src/__generated__/core/DatumGraphNodeQuery.graphql.ts index a7839444b..efa2ae4b3 100644 --- a/apps/console/src/__generated__/core/DatumGraphNodeQuery.graphql.ts +++ b/apps/console/src/__generated__/core/DatumGraphNodeQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<5cfbdbc980894977800a18064306f2c1>> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -16,6 +16,8 @@ export type DatumGraphNodeQuery$variables = { }; export type DatumGraphNodeQuery$data = { readonly node: { + readonly canDelete?: boolean; + readonly canUpdate?: boolean; readonly createdAt?: any; readonly dataClassification?: DataClassification; readonly id?: string; @@ -188,6 +190,32 @@ v10 = { "kind": "ScalarField", "name": "updatedAt", "storageKey": null +}, +v11 = { + "alias": "canUpdate", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "core:datum:update" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"core:datum:update\")" +}, +v12 = { + "alias": "canDelete", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "core:datum:delete" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"core:datum:delete\")" }; return { "fragment": { @@ -215,7 +243,9 @@ return { (v7/*: any*/), (v8/*: any*/), (v9/*: any*/), - (v10/*: any*/) + (v10/*: any*/), + (v11/*: any*/), + (v12/*: any*/) ], "type": "Datum", "abstractKey": null @@ -259,7 +289,9 @@ return { (v7/*: any*/), (v8/*: any*/), (v9/*: any*/), - (v10/*: any*/) + (v10/*: any*/), + (v11/*: any*/), + (v12/*: any*/) ], "type": "Datum", "abstractKey": null @@ -270,16 +302,16 @@ return { ] }, "params": { - "cacheID": "6ed49a3413c0214dc95da9a422e484b4", + "cacheID": "036588bb9b3daffb20c9d01a708976ac", "id": null, "metadata": {}, "name": "DatumGraphNodeQuery", "operationKind": "query", - "text": "query DatumGraphNodeQuery(\n $dataId: ID!\n) {\n node(id: $dataId) {\n __typename\n ... on Datum {\n id\n snapshotId\n name\n dataClassification\n owner {\n id\n fullName\n }\n vendors(first: 50) {\n edges {\n node {\n id\n name\n websiteUrl\n category\n }\n }\n }\n organization {\n id\n }\n createdAt\n updatedAt\n }\n id\n }\n}\n" + "text": "query DatumGraphNodeQuery(\n $dataId: ID!\n) {\n node(id: $dataId) {\n __typename\n ... on Datum {\n id\n snapshotId\n name\n dataClassification\n owner {\n id\n fullName\n }\n vendors(first: 50) {\n edges {\n node {\n id\n name\n websiteUrl\n category\n }\n }\n }\n organization {\n id\n }\n createdAt\n updatedAt\n canUpdate: permission(action: \"core:datum:update\")\n canDelete: permission(action: \"core:datum:delete\")\n }\n id\n }\n}\n" } }; })(); -(node as any).hash = "2f4df20b67f8b21eda5f510f250abda1"; +(node as any).hash = "2b2e220e0bdd98bbee7ea72a00db7a61"; export default node; diff --git a/apps/console/src/hooks/graph/DatumGraph.ts b/apps/console/src/hooks/graph/DatumGraph.ts index 0326da523..3a033abe6 100644 --- a/apps/console/src/hooks/graph/DatumGraph.ts +++ b/apps/console/src/hooks/graph/DatumGraph.ts @@ -8,6 +8,7 @@ export const dataQuery = graphql` query DatumGraphListQuery($organizationId: ID!, $snapshotId: ID = null) { node(id: $organizationId) { ... on Organization { + canCreateDatum: permission(action: "core:datum:create") ...DataPageFragment @arguments(snapshotId: $snapshotId) } } @@ -41,6 +42,8 @@ export const datumNodeQuery = graphql` } createdAt updatedAt + canUpdate: permission(action: "core:datum:update") + canDelete: permission(action: "core:datum:delete") } } } @@ -71,6 +74,8 @@ export const createDatumMutation = graphql` } } createdAt + canUpdate: permission(action: "core:datum:update") + canDelete: permission(action: "core:datum:delete") } } } @@ -116,7 +121,7 @@ export const deleteDatumMutation = graphql` export const useDeleteDatum = ( datum: { id?: string; name?: string }, - connectionId: string + connectionId: string, ) => { const [mutate] = useMutation(deleteDatumMutation); const confirm = useConfirm(); @@ -139,11 +144,11 @@ export const useDeleteDatum = ( { message: sprintf( __( - 'This will permanently delete "%s". This action cannot be undone.' + 'This will permanently delete "%s". This action cannot be undone.', ), - datum.name + datum.name, ), - } + }, ); }; }; diff --git a/apps/console/src/pages/organizations/data/DataPage.tsx b/apps/console/src/pages/organizations/data/DataPage.tsx index 8cf856466..05b16c248 100644 --- a/apps/console/src/pages/organizations/data/DataPage.tsx +++ b/apps/console/src/pages/organizations/data/DataPage.tsx @@ -35,8 +35,6 @@ import type { import type { DataListQuery } from "/__generated__/core/DataListQuery.graphql"; import { SortableTable } from "/components/SortableTable"; import { SnapshotBanner } from "/components/SnapshotBanner"; -import { use } from "react"; -import { PermissionsContext } from "/providers/PermissionsContext"; const paginatedDataFragment = graphql` fragment DataPageFragment on Organization @@ -76,6 +74,8 @@ const paginatedDataFragment = graphql` } } createdAt + canUpdate: permission(action: "core:datum:update") + canDelete: permission(action: "core:datum:delete") } } } @@ -93,15 +93,12 @@ export default function DataPage(props: Props) { const organizationId = useOrganizationId(); const { snapshotId } = useParams<{ snapshotId?: string }>(); const isSnapshotMode = Boolean(snapshotId); - const { isAuthorized } = use(PermissionsContext); - const queryData = usePreloadedQuery( + const { node: data } = usePreloadedQuery( dataQuery, props.queryRef, ); - const data = queryData.node; - const pagination = usePaginationFragment( paginatedDataFragment, data, @@ -128,8 +125,7 @@ export default function DataPage(props: Props) { const hasAnyAction = !isSnapshotMode && - (isAuthorized("Datum", "updateDatum") || - isAuthorized("Datum", "deleteDatum")); + dataEntries.some(({ canDelete, canUpdate }) => canUpdate || canDelete); return (
@@ -142,7 +138,7 @@ export default function DataPage(props: Props) { "Manage your organization's data assets and their classifications.", )} > - {!snapshotId && isAuthorized("Organization", "createDatum") && ( + {!snapshotId && data.canCreateDatum && ( edge.node) ?? []; - const { isAuthorized } = use(PermissionsContext); const detailUrl = snapshotId ? `/organizations/${organizationId}/snapshots/${snapshotId}/data/${entry.id}` : `/organizations/${organizationId}/data/${entry.id}`; @@ -235,7 +230,7 @@ function DataRow({ {hasAnyAction && ( - {isAuthorized("Datum", "deleteDatum") && ( + {entry.canDelete && ( {isSnapshotMode && snapshotId && ( @@ -124,7 +123,7 @@ export default function DatumDetailsPage(props: Props) {
{datumEntry?.name}
{datumEntry?.dataClassification}
- {!isSnapshotMode && isAuthorized("Datum", "deleteDatum") && ( + {!isSnapshotMode && datumEntry.canDelete && ( @@ -163,7 +162,7 @@ export default function DatumDetailsPage(props: Props) { control={control} name="ownerId" label={__("Owner")} - disabled={isSnapshotMode} + disabled={!disabled} /> {!isSnapshotMode && (
- {formState.isDirty && isAuthorized("Datum", "updateDatum") && ( + {formState.isDirty && datumEntry.canUpdate && ( diff --git a/pkg/server/api/console/v1/schema.graphql b/pkg/server/api/console/v1/schema.graphql index 4ccd5a799..28bcd8cd8 100644 --- a/pkg/server/api/console/v1/schema.graphql +++ b/pkg/server/api/console/v1/schema.graphql @@ -4972,6 +4972,8 @@ type Datum implements Node organization: Organization! @goField(forceResolver: true) createdAt: Datetime! updatedAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) } input DatumOrder diff --git a/pkg/server/api/console/v1/schema/schema.go b/pkg/server/api/console/v1/schema/schema.go index 4ae2e3743..f5ac8b90a 100644 --- a/pkg/server/api/console/v1/schema/schema.go +++ b/pkg/server/api/console/v1/schema/schema.go @@ -494,6 +494,7 @@ type ComplexityRoot struct { Name func(childComplexity int) int Organization func(childComplexity int) int Owner func(childComplexity int) int + Permission func(childComplexity int, action string) int SnapshotID func(childComplexity int) int UpdatedAt func(childComplexity int) int Vendors func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.VendorOrderBy) int @@ -2005,6 +2006,8 @@ type DatumResolver interface { Owner(ctx context.Context, obj *types.Datum) (*types.People, error) Vendors(ctx context.Context, obj *types.Datum, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.VendorOrderBy) (*types.VendorConnection, error) Organization(ctx context.Context, obj *types.Datum) (*types.Organization, error) + + Permission(ctx context.Context, obj *types.Datum, action string) (bool, error) } type DatumConnectionResolver interface { TotalCount(ctx context.Context, obj *types.DatumConnection) (int, error) @@ -3709,6 +3712,17 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin } return e.complexity.Datum.Owner(childComplexity), true + case "Datum.permission": + if e.complexity.Datum.Permission == nil { + break + } + + args, err := ec.field_Datum_permission_args(ctx, rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Datum.Permission(childComplexity, args["action"].(string)), true case "Datum.snapshotId": if e.complexity.Datum.SnapshotID == nil { break @@ -15314,6 +15328,8 @@ type Datum implements Node organization: Organization! @goField(forceResolver: true) createdAt: Datetime! updatedAt: Datetime! + + permission(action: String!): Boolean! @goField(forceResolver: true) } input DatumOrder @@ -15876,6 +15892,17 @@ func (ec *executionContext) field_DataProtectionImpactAssessment_permission_args return args, nil } +func (ec *executionContext) field_Datum_permission_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := graphql.ProcessArgField(ctx, rawArgs, "action", ec.unmarshalNString2string) + if err != nil { + return nil, err + } + args["action"] = arg0 + return args, nil +} + func (ec *executionContext) field_Datum_vendors_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} @@ -26455,6 +26482,47 @@ func (ec *executionContext) fieldContext_Datum_updatedAt(_ context.Context, fiel return fc, nil } +func (ec *executionContext) _Datum_permission(ctx context.Context, field graphql.CollectedField, obj *types.Datum) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_Datum_permission, + func(ctx context.Context) (any, error) { + fc := graphql.GetFieldContext(ctx) + return ec.resolvers.Datum().Permission(ctx, obj, fc.Args["action"].(string)) + }, + nil, + ec.marshalNBoolean2bool, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_Datum_permission(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Datum", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Boolean does not have child fields") + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Datum_permission_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + func (ec *executionContext) _DatumConnection_totalCount(ctx context.Context, field graphql.CollectedField, obj *types.DatumConnection) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -26629,6 +26697,8 @@ func (ec *executionContext) fieldContext_DatumEdge_node(_ context.Context, field return ec.fieldContext_Datum_createdAt(ctx, field) case "updatedAt": return ec.fieldContext_Datum_updatedAt(ctx, field) + case "permission": + return ec.fieldContext_Datum_permission(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type Datum", field.Name) }, @@ -53394,6 +53464,8 @@ func (ec *executionContext) fieldContext_UpdateDatumPayload_datum(_ context.Cont return ec.fieldContext_Datum_createdAt(ctx, field) case "updatedAt": return ec.fieldContext_Datum_updatedAt(ctx, field) + case "permission": + return ec.fieldContext_Datum_permission(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type Datum", field.Name) }, @@ -73399,6 +73471,42 @@ func (ec *executionContext) _Datum(ctx context.Context, sel ast.SelectionSet, ob if out.Values[i] == graphql.Null { atomic.AddUint32(&out.Invalids, 1) } + case "permission": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Datum_permission(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) default: panic("unknown field " + strconv.Quote(field.Name)) } diff --git a/pkg/server/api/console/v1/types/datum.go b/pkg/server/api/console/v1/types/datum.go index c48e1e1bd..c91802158 100644 --- a/pkg/server/api/console/v1/types/datum.go +++ b/pkg/server/api/console/v1/types/datum.go @@ -79,6 +79,9 @@ func NewDatum(d *coredata.Datum) *Datum { Organization: &Organization{ ID: d.OrganizationID, }, + Owner: &People{ + ID: d.OwnerID, + }, OrganizationID: d.OrganizationID, Name: d.Name, SnapshotID: d.SnapshotID, diff --git a/pkg/server/api/console/v1/v1_resolver.go b/pkg/server/api/console/v1/v1_resolver.go index 2245afa07..99889f4fa 100644 --- a/pkg/server/api/console/v1/v1_resolver.go +++ b/pkg/server/api/console/v1/v1_resolver.go @@ -702,6 +702,11 @@ func (r *datumResolver) Organization(ctx context.Context, obj *types.Datum) (*ty return types.NewOrganization(org), nil } +// Permission is the resolver for the permission field. +func (r *datumResolver) Permission(ctx context.Context, obj *types.Datum, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + // TotalCount is the resolver for the totalCount field. func (r *datumConnectionResolver) TotalCount(ctx context.Context, obj *types.DatumConnection) (int, error) { r.MustAuthorize(ctx, obj.ParentID, probo.ActionDatumList)