Add assets snapshots

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2025-08-29 19:05:17 +02:00
parent 612149afc8
commit 89036d3a0f
25 changed files with 839 additions and 186 deletions

View File

@@ -5,10 +5,10 @@ import { useTranslate } from "@probo/i18n";
import { promisifyMutation, sprintf } from "@probo/helpers"; import { promisifyMutation, sprintf } from "@probo/helpers";
export const assetsQuery = graphql` export const assetsQuery = graphql`
query AssetGraphListQuery($organizationId: ID!) { query AssetGraphListQuery($organizationId: ID!, $snapshotId: ID) {
node(id: $organizationId) { node(id: $organizationId) {
... on Organization { ... on Organization {
...AssetsPageFragment ...AssetsPageFragment @arguments(snapshotId: $snapshotId)
} }
} }
} }
@@ -19,6 +19,7 @@ export const assetNodeQuery = graphql`
node(id: $assetId) { node(id: $assetId) {
... on Asset { ... on Asset {
id id
snapshotId
name name
amount amount
criticity criticity
@@ -54,6 +55,7 @@ export const createAssetMutation = graphql`
assetEdge @prependEdge(connections: $connections) { assetEdge @prependEdge(connections: $connections) {
node { node {
id id
snapshotId
name name
amount amount
criticity criticity
@@ -63,7 +65,7 @@ export const createAssetMutation = graphql`
id id
fullName fullName
} }
vendors(first: 10) { vendors(first: 50) {
edges { edges {
node { node {
id id
@@ -84,6 +86,7 @@ export const updateAssetMutation = graphql`
updateAsset(input: $input) { updateAsset(input: $input) {
asset { asset {
id id
snapshotId
name name
amount amount
criticity criticity

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<8ddee1e0195c3af2818a78e1224ad297>> * @generated SignedSource<<9f6605e9adebeee2a681da0278642501>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -40,6 +40,7 @@ export type AssetGraphCreateMutation$data = {
readonly fullName: string; readonly fullName: string;
readonly id: string; readonly id: string;
}; };
readonly snapshotId: string | null | undefined;
readonly vendors: { readonly vendors: {
readonly edges: ReadonlyArray<{ readonly edges: ReadonlyArray<{
readonly node: { readonly node: {
@@ -107,6 +108,13 @@ v5 = {
"plural": false, "plural": false,
"selections": [ "selections": [
(v3/*: any*/), (v3/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "snapshotId",
"storageKey": null
},
(v4/*: any*/), (v4/*: any*/),
{ {
"alias": null, "alias": null,
@@ -161,7 +169,7 @@ v5 = {
{ {
"kind": "Literal", "kind": "Literal",
"name": "first", "name": "first",
"value": 10 "value": 50
} }
], ],
"concreteType": "VendorConnection", "concreteType": "VendorConnection",
@@ -201,7 +209,7 @@ v5 = {
"storageKey": null "storageKey": null
} }
], ],
"storageKey": "vendors(first:10)" "storageKey": "vendors(first:50)"
}, },
{ {
"alias": null, "alias": null,
@@ -282,16 +290,16 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "f1d2e4e85879e08931d9d6815cc51ec1", "cacheID": "f11f5052ed6dd175bd701812c0cd136b",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "AssetGraphCreateMutation", "name": "AssetGraphCreateMutation",
"operationKind": "mutation", "operationKind": "mutation",
"text": "mutation AssetGraphCreateMutation(\n $input: CreateAssetInput!\n) {\n createAsset(input: $input) {\n assetEdge {\n node {\n id\n name\n amount\n criticity\n assetType\n dataTypesStored\n owner {\n id\n fullName\n }\n vendors(first: 10) {\n edges {\n node {\n id\n name\n websiteUrl\n }\n }\n }\n createdAt\n }\n }\n }\n}\n" "text": "mutation AssetGraphCreateMutation(\n $input: CreateAssetInput!\n) {\n createAsset(input: $input) {\n assetEdge {\n node {\n id\n snapshotId\n name\n amount\n criticity\n assetType\n dataTypesStored\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"
} }
}; };
})(); })();
(node as any).hash = "5053ed385668e9f51d916a194ea8936d"; (node as any).hash = "3cec119022d8dffb8536d93294e2633b";
export default node; export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<8758668524dc47bc04757126d7f423b3>> * @generated SignedSource<<8d257a13d23e2dd68542540dd12ca35b>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -12,6 +12,7 @@ import { ConcreteRequest } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime"; import { FragmentRefs } from "relay-runtime";
export type AssetGraphListQuery$variables = { export type AssetGraphListQuery$variables = {
organizationId: string; organizationId: string;
snapshotId?: string | null | undefined;
}; };
export type AssetGraphListQuery$data = { export type AssetGraphListQuery$data = {
readonly node: { readonly node: {
@@ -29,6 +30,11 @@ var v0 = [
"defaultValue": null, "defaultValue": null,
"kind": "LocalArgument", "kind": "LocalArgument",
"name": "organizationId" "name": "organizationId"
},
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "snapshotId"
} }
], ],
v1 = [ v1 = [
@@ -38,28 +44,40 @@ v1 = [
"variableName": "organizationId" "variableName": "organizationId"
} }
], ],
v2 = { v2 = [
{
"kind": "Variable",
"name": "snapshotId",
"variableName": "snapshotId"
}
],
v3 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "__typename", "name": "__typename",
"storageKey": null "storageKey": null
}, },
v3 = { v4 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "id", "name": "id",
"storageKey": null "storageKey": null
}, },
v4 = [ v5 = [
{
"fields": (v2/*: any*/),
"kind": "ObjectValue",
"name": "filter"
},
{ {
"kind": "Literal", "kind": "Literal",
"name": "first", "name": "first",
"value": 10 "value": 10
} }
], ],
v5 = { v6 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
@@ -85,7 +103,7 @@ return {
"kind": "InlineFragment", "kind": "InlineFragment",
"selections": [ "selections": [
{ {
"args": null, "args": (v2/*: any*/),
"kind": "FragmentSpread", "kind": "FragmentSpread",
"name": "AssetsPageFragment" "name": "AssetsPageFragment"
} }
@@ -114,14 +132,14 @@ return {
"name": "node", "name": "node",
"plural": false, "plural": false,
"selections": [ "selections": [
(v2/*: any*/),
(v3/*: any*/), (v3/*: any*/),
(v4/*: any*/),
{ {
"kind": "InlineFragment", "kind": "InlineFragment",
"selections": [ "selections": [
{ {
"alias": null, "alias": null,
"args": (v4/*: any*/), "args": (v5/*: any*/),
"concreteType": "AssetConnection", "concreteType": "AssetConnection",
"kind": "LinkedField", "kind": "LinkedField",
"name": "assets", "name": "assets",
@@ -143,8 +161,15 @@ return {
"name": "node", "name": "node",
"plural": false, "plural": false,
"selections": [ "selections": [
(v3/*: any*/), (v4/*: any*/),
(v5/*: any*/), {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "snapshotId",
"storageKey": null
},
(v6/*: any*/),
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -181,7 +206,7 @@ return {
"name": "owner", "name": "owner",
"plural": false, "plural": false,
"selections": [ "selections": [
(v3/*: any*/), (v4/*: any*/),
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -222,8 +247,8 @@ return {
"name": "node", "name": "node",
"plural": false, "plural": false,
"selections": [ "selections": [
(v3/*: any*/), (v4/*: any*/),
(v5/*: any*/), (v6/*: any*/),
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -247,7 +272,7 @@ return {
"name": "createdAt", "name": "createdAt",
"storageKey": null "storageKey": null
}, },
(v2/*: any*/) (v3/*: any*/)
], ],
"storageKey": null "storageKey": null
}, },
@@ -313,13 +338,13 @@ return {
] ]
} }
], ],
"storageKey": "assets(first:10)" "storageKey": null
}, },
{ {
"alias": null, "alias": null,
"args": (v4/*: any*/), "args": (v5/*: any*/),
"filters": [ "filters": [
"orderBy" "filter"
], ],
"handle": "connection", "handle": "connection",
"key": "AssetsPage_assets", "key": "AssetsPage_assets",
@@ -336,16 +361,16 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "7e293e732d5ff8246a1ed772e1c33984", "cacheID": "29eec1243d3cceb5d9931562635e1f24",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "AssetGraphListQuery", "name": "AssetGraphListQuery",
"operationKind": "query", "operationKind": "query",
"text": "query AssetGraphListQuery(\n $organizationId: ID!\n) {\n node(id: $organizationId) {\n __typename\n ... on Organization {\n ...AssetsPageFragment\n }\n id\n }\n}\n\nfragment AssetsPageFragment on Organization {\n assets(first: 10) {\n edges {\n node {\n id\n name\n amount\n criticity\n assetType\n dataTypesStored\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 __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n" "text": "query AssetGraphListQuery(\n $organizationId: ID!\n $snapshotId: ID\n) {\n node(id: $organizationId) {\n __typename\n ... on Organization {\n ...AssetsPageFragment_3iomuz\n }\n id\n }\n}\n\nfragment AssetsPageFragment_3iomuz on Organization {\n assets(first: 10, filter: {snapshotId: $snapshotId}) {\n edges {\n node {\n id\n snapshotId\n name\n amount\n criticity\n assetType\n dataTypesStored\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 __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 = "88644d5fcb5f2e017df4b5d6373ce27f"; (node as any).hash = "4cbb1234b0884823285d3660334a2b29";
export default node; export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<bdc352ae0c426ad29ff6e0f53dc357cf>> * @generated SignedSource<<f1d88685da6d13c894f029b8ac4cf3e4>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -28,6 +28,7 @@ export type AssetGraphNodeQuery$data = {
readonly fullName: string; readonly fullName: string;
readonly id: string; readonly id: string;
}; };
readonly snapshotId?: string | null | undefined;
readonly updatedAt?: any; readonly updatedAt?: any;
readonly vendors?: { readonly vendors?: {
readonly edges: ReadonlyArray<{ readonly edges: ReadonlyArray<{
@@ -72,38 +73,45 @@ v3 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "name", "name": "snapshotId",
"storageKey": null "storageKey": null
}, },
v4 = { v4 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "amount", "name": "name",
"storageKey": null "storageKey": null
}, },
v5 = { v5 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "criticity", "name": "amount",
"storageKey": null "storageKey": null
}, },
v6 = { v6 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "assetType", "name": "criticity",
"storageKey": null "storageKey": null
}, },
v7 = { v7 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "dataTypesStored", "name": "assetType",
"storageKey": null "storageKey": null
}, },
v8 = { v8 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "dataTypesStored",
"storageKey": null
},
v9 = {
"alias": null, "alias": null,
"args": null, "args": null,
"concreteType": "People", "concreteType": "People",
@@ -122,7 +130,7 @@ v8 = {
], ],
"storageKey": null "storageKey": null
}, },
v9 = { v10 = {
"alias": null, "alias": null,
"args": [ "args": [
{ {
@@ -153,7 +161,7 @@ v9 = {
"plural": false, "plural": false,
"selections": [ "selections": [
(v2/*: any*/), (v2/*: any*/),
(v3/*: any*/), (v4/*: any*/),
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -177,14 +185,14 @@ v9 = {
], ],
"storageKey": "vendors(first:50)" "storageKey": "vendors(first:50)"
}, },
v10 = { v11 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "createdAt", "name": "createdAt",
"storageKey": null "storageKey": null
}, },
v11 = { v12 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
@@ -218,7 +226,8 @@ return {
(v8/*: any*/), (v8/*: any*/),
(v9/*: any*/), (v9/*: any*/),
(v10/*: any*/), (v10/*: any*/),
(v11/*: any*/) (v11/*: any*/),
(v12/*: any*/)
], ],
"type": "Asset", "type": "Asset",
"abstractKey": null "abstractKey": null
@@ -263,7 +272,8 @@ return {
(v8/*: any*/), (v8/*: any*/),
(v9/*: any*/), (v9/*: any*/),
(v10/*: any*/), (v10/*: any*/),
(v11/*: any*/) (v11/*: any*/),
(v12/*: any*/)
], ],
"type": "Asset", "type": "Asset",
"abstractKey": null "abstractKey": null
@@ -274,16 +284,16 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "cd1d7c48ccf01acee749af3b8948fb9a", "cacheID": "6c6e10d25c9bd116703346393adb7a3f",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "AssetGraphNodeQuery", "name": "AssetGraphNodeQuery",
"operationKind": "query", "operationKind": "query",
"text": "query AssetGraphNodeQuery(\n $assetId: ID!\n) {\n node(id: $assetId) {\n __typename\n ... on Asset {\n id\n name\n amount\n criticity\n assetType\n dataTypesStored\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 createdAt\n updatedAt\n }\n id\n }\n}\n" "text": "query AssetGraphNodeQuery(\n $assetId: ID!\n) {\n node(id: $assetId) {\n __typename\n ... on Asset {\n id\n snapshotId\n name\n amount\n criticity\n assetType\n dataTypesStored\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 createdAt\n updatedAt\n }\n id\n }\n}\n"
} }
}; };
})(); })();
(node as any).hash = "47e84c9ea1c9ab9310ad493219c12f3f"; (node as any).hash = "60053f70c9a65d4658be7b830a3dae72";
export default node; export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<ead1e328f3bc8ddc8f4c98cee6838ee9>> * @generated SignedSource<<6bc692e9d9ab5b78611dc2a326227761>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -37,6 +37,7 @@ export type AssetGraphUpdateMutation$data = {
readonly fullName: string; readonly fullName: string;
readonly id: string; readonly id: string;
}; };
readonly snapshotId: string | null | undefined;
readonly updatedAt: any; readonly updatedAt: any;
readonly vendors: { readonly vendors: {
readonly edges: ReadonlyArray<{ readonly edges: ReadonlyArray<{
@@ -101,6 +102,13 @@ v3 = [
"plural": false, "plural": false,
"selections": [ "selections": [
(v1/*: any*/), (v1/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "snapshotId",
"storageKey": null
},
(v2/*: any*/), (v2/*: any*/),
{ {
"alias": null, "alias": null,
@@ -229,16 +237,16 @@ return {
"selections": (v3/*: any*/) "selections": (v3/*: any*/)
}, },
"params": { "params": {
"cacheID": "f5e4ccc38f842f4965c32676ae407012", "cacheID": "1447ea6f965bbd6fcdba1ebc72e2d23f",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "AssetGraphUpdateMutation", "name": "AssetGraphUpdateMutation",
"operationKind": "mutation", "operationKind": "mutation",
"text": "mutation AssetGraphUpdateMutation(\n $input: UpdateAssetInput!\n) {\n updateAsset(input: $input) {\n asset {\n id\n name\n amount\n criticity\n assetType\n dataTypesStored\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 updatedAt\n }\n }\n}\n" "text": "mutation AssetGraphUpdateMutation(\n $input: UpdateAssetInput!\n) {\n updateAsset(input: $input) {\n asset {\n id\n snapshotId\n name\n amount\n criticity\n assetType\n dataTypesStored\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 updatedAt\n }\n }\n}\n"
} }
}; };
})(); })();
(node as any).hash = "257fa8f1a9996808403fa65a06b61ba2"; (node as any).hash = "21b0c94b6245eb849e6118a58fb97490";
export default node; export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<4453e7e28a9c546de765219f287f129a>> * @generated SignedSource<<676223ac3bde5a9edf7e3d68fe3ed981>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -38,7 +38,6 @@ export type ProcessingActivityRegistryGraphNodeQuery$data = {
readonly retentionPeriod?: string | null | undefined; readonly retentionPeriod?: string | null | undefined;
readonly securityMeasures?: string | null | undefined; readonly securityMeasures?: string | null | undefined;
readonly snapshotId?: string | null | undefined; readonly snapshotId?: string | null | undefined;
readonly sourceId?: string | null | undefined;
readonly specialOrCriminalData?: ProcessingActivityRegistrySpecialOrCriminalData; readonly specialOrCriminalData?: ProcessingActivityRegistrySpecialOrCriminalData;
readonly transferImpactAssessment?: ProcessingActivityRegistryTransferImpactAssessment; readonly transferImpactAssessment?: ProcessingActivityRegistryTransferImpactAssessment;
readonly transferSafeguards?: ProcessingActivityRegistryTransferSafeguards | null | undefined; readonly transferSafeguards?: ProcessingActivityRegistryTransferSafeguards | null | undefined;
@@ -83,115 +82,108 @@ v4 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "sourceId", "name": "name",
"storageKey": null "storageKey": null
}, },
v5 = { v5 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "name", "name": "purpose",
"storageKey": null "storageKey": null
}, },
v6 = { v6 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "purpose", "name": "dataSubjectCategory",
"storageKey": null "storageKey": null
}, },
v7 = { v7 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "dataSubjectCategory", "name": "personalDataCategory",
"storageKey": null "storageKey": null
}, },
v8 = { v8 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "personalDataCategory", "name": "specialOrCriminalData",
"storageKey": null "storageKey": null
}, },
v9 = { v9 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "specialOrCriminalData", "name": "consentEvidenceLink",
"storageKey": null "storageKey": null
}, },
v10 = { v10 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "consentEvidenceLink", "name": "lawfulBasis",
"storageKey": null "storageKey": null
}, },
v11 = { v11 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "lawfulBasis", "name": "recipients",
"storageKey": null "storageKey": null
}, },
v12 = { v12 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "recipients", "name": "location",
"storageKey": null "storageKey": null
}, },
v13 = { v13 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "location", "name": "internationalTransfers",
"storageKey": null "storageKey": null
}, },
v14 = { v14 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "internationalTransfers", "name": "transferSafeguards",
"storageKey": null "storageKey": null
}, },
v15 = { v15 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "transferSafeguards", "name": "retentionPeriod",
"storageKey": null "storageKey": null
}, },
v16 = { v16 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "retentionPeriod", "name": "securityMeasures",
"storageKey": null "storageKey": null
}, },
v17 = { v17 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "securityMeasures", "name": "dataProtectionImpactAssessment",
"storageKey": null "storageKey": null
}, },
v18 = { v18 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "dataProtectionImpactAssessment",
"storageKey": null
},
v19 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "transferImpactAssessment", "name": "transferImpactAssessment",
"storageKey": null "storageKey": null
}, },
v20 = { v19 = {
"alias": null, "alias": null,
"args": null, "args": null,
"concreteType": "Organization", "concreteType": "Organization",
@@ -200,18 +192,18 @@ v20 = {
"plural": false, "plural": false,
"selections": [ "selections": [
(v2/*: any*/), (v2/*: any*/),
(v5/*: any*/) (v4/*: any*/)
], ],
"storageKey": null "storageKey": null
}, },
v21 = { v20 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "createdAt", "name": "createdAt",
"storageKey": null "storageKey": null
}, },
v22 = { v21 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
@@ -255,8 +247,7 @@ return {
(v18/*: any*/), (v18/*: any*/),
(v19/*: any*/), (v19/*: any*/),
(v20/*: any*/), (v20/*: any*/),
(v21/*: any*/), (v21/*: any*/)
(v22/*: any*/)
], ],
"type": "ProcessingActivityRegistry", "type": "ProcessingActivityRegistry",
"abstractKey": null "abstractKey": null
@@ -311,8 +302,7 @@ return {
(v18/*: any*/), (v18/*: any*/),
(v19/*: any*/), (v19/*: any*/),
(v20/*: any*/), (v20/*: any*/),
(v21/*: any*/), (v21/*: any*/)
(v22/*: any*/)
], ],
"type": "ProcessingActivityRegistry", "type": "ProcessingActivityRegistry",
"abstractKey": null "abstractKey": null
@@ -323,16 +313,16 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "b8a3edd61ea5d94cc3594f40f181d27f", "cacheID": "10b9608f3b93e930c05f415a0ac9fbec",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "ProcessingActivityRegistryGraphNodeQuery", "name": "ProcessingActivityRegistryGraphNodeQuery",
"operationKind": "query", "operationKind": "query",
"text": "query ProcessingActivityRegistryGraphNodeQuery(\n $processingActivityRegistryId: ID!\n) {\n node(id: $processingActivityRegistryId) {\n __typename\n ... on ProcessingActivityRegistry {\n id\n snapshotId\n sourceId\n name\n purpose\n dataSubjectCategory\n personalDataCategory\n specialOrCriminalData\n consentEvidenceLink\n lawfulBasis\n recipients\n location\n internationalTransfers\n transferSafeguards\n retentionPeriod\n securityMeasures\n dataProtectionImpactAssessment\n transferImpactAssessment\n organization {\n id\n name\n }\n createdAt\n updatedAt\n }\n id\n }\n}\n" "text": "query ProcessingActivityRegistryGraphNodeQuery(\n $processingActivityRegistryId: ID!\n) {\n node(id: $processingActivityRegistryId) {\n __typename\n ... on ProcessingActivityRegistry {\n id\n snapshotId\n name\n purpose\n dataSubjectCategory\n personalDataCategory\n specialOrCriminalData\n consentEvidenceLink\n lawfulBasis\n recipients\n location\n internationalTransfers\n transferSafeguards\n retentionPeriod\n securityMeasures\n dataProtectionImpactAssessment\n transferImpactAssessment\n organization {\n id\n name\n }\n createdAt\n updatedAt\n }\n id\n }\n}\n"
} }
}; };
})(); })();
(node as any).hash = "6714c1965f54321530f73a5b7ef86fa6"; (node as any).hash = "c1728cff79a3773498b383bb32728cf6";
export default node; export default node;

View File

@@ -20,12 +20,14 @@ import {
} from "@probo/ui"; } from "@probo/ui";
import { useTranslate } from "@probo/i18n"; import { useTranslate } from "@probo/i18n";
import { useOrganizationId } from "/hooks/useOrganizationId"; import { useOrganizationId } from "/hooks/useOrganizationId";
import { useParams } from "react-router";
import { ControlledField } from "/components/form/ControlledField"; import { ControlledField } from "/components/form/ControlledField";
import { PeopleSelectField } from "/components/form/PeopleSelectField"; import { PeopleSelectField } from "/components/form/PeopleSelectField";
import { VendorsMultiSelectField } from "/components/form/VendorsMultiSelectField"; import { VendorsMultiSelectField } from "/components/form/VendorsMultiSelectField";
import { useFormWithSchema } from "/hooks/useFormWithSchema"; import { useFormWithSchema } from "/hooks/useFormWithSchema";
import z from "zod"; import z from "zod";
import { getAssetTypeVariant, getCriticityVariant } from "@probo/helpers"; import { getAssetTypeVariant, getCriticityVariant, validateSnapshotConsistency } from "@probo/helpers";
import { SnapshotBanner } from "/components/SnapshotBanner";
const updateAssetSchema = z.object({ const updateAssetSchema = z.object({
name: z.string().min(1, "Name is required"), name: z.string().min(1, "Name is required"),
@@ -46,10 +48,21 @@ export default function AssetDetailsPage(props: Props) {
const assetEntry = asset.node; const assetEntry = asset.node;
const { __ } = useTranslate(); const { __ } = useTranslate();
const organizationId = useOrganizationId(); const organizationId = useOrganizationId();
const deleteAsset = useDeleteAsset( const { snapshotId } = useParams<{ snapshotId?: string }>();
assetEntry, const isSnapshotMode = Boolean(snapshotId);
ConnectionHandler.getConnectionID(organizationId, "AssetsPage_assets")
if (!assetEntry) {
return <div>{__("Asset not found")}</div>;
}
validateSnapshotConsistency(assetEntry, snapshotId);
const connectionId = ConnectionHandler.getConnectionID(
organizationId,
"AssetsPage_assets",
{ filter: { snapshotId: snapshotId || null } }
); );
const deleteAsset = useDeleteAsset(assetEntry, connectionId);
const vendors = assetEntry?.vendors?.edges.map((edge: any) => edge.node) ?? []; const vendors = assetEntry?.vendors?.edges.map((edge: any) => edge.node) ?? [];
const vendorIds = vendors.map((vendor: any) => vendor.id); const vendorIds = vendors.map((vendor: any) => vendor.id);
@@ -80,13 +93,18 @@ export default function AssetDetailsPage(props: Props) {
} }
}); });
const breadcrumbAssetsUrl = isSnapshotMode && snapshotId
? `/organizations/${organizationId}/snapshots/${snapshotId}/assets`
: `/organizations/${organizationId}/assets`;
return ( return (
<div className="space-y-6"> <div className="space-y-6">
{snapshotId && <SnapshotBanner snapshotId={snapshotId} />}
<Breadcrumb <Breadcrumb
items={[ items={[
{ {
label: __("Assets"), label: __("Assets"),
to: `/organizations/${organizationId}/assets`, to: breadcrumbAssetsUrl,
}, },
{ {
label: assetEntry?.name ?? "", label: assetEntry?.name ?? "",
@@ -104,6 +122,7 @@ export default function AssetDetailsPage(props: Props) {
{assetEntry?.criticity} {assetEntry?.criticity}
</Badge> </Badge>
</div> </div>
{!isSnapshotMode && (
<ActionDropdown variant="secondary"> <ActionDropdown variant="secondary">
<DropdownItem <DropdownItem
variant="danger" variant="danger"
@@ -113,6 +132,7 @@ export default function AssetDetailsPage(props: Props) {
{__("Delete")} {__("Delete")}
</DropdownItem> </DropdownItem>
</ActionDropdown> </ActionDropdown>
)}
</div> </div>
<form onSubmit={onSubmit} className="space-y-6 max-w-2xl"> <form onSubmit={onSubmit} className="space-y-6 max-w-2xl">
@@ -120,12 +140,14 @@ export default function AssetDetailsPage(props: Props) {
label={__("Name")} label={__("Name")}
{...register("name")} {...register("name")}
type="text" type="text"
disabled={isSnapshotMode}
/> />
<Field <Field
label={__("Amount")} label={__("Amount")}
{...register("amount", { valueAsNumber: true })} {...register("amount", { valueAsNumber: true })}
type="number" type="number"
disabled={isSnapshotMode}
/> />
<ControlledField <ControlledField
@@ -133,6 +155,7 @@ export default function AssetDetailsPage(props: Props) {
name="criticity" name="criticity"
type="select" type="select"
label={__("Criticity")} label={__("Criticity")}
disabled={isSnapshotMode}
> >
<Option value="LOW">{__("Low")}</Option> <Option value="LOW">{__("Low")}</Option>
<Option value="MEDIUM">{__("Medium")}</Option> <Option value="MEDIUM">{__("Medium")}</Option>
@@ -144,6 +167,7 @@ export default function AssetDetailsPage(props: Props) {
name="assetType" name="assetType"
type="select" type="select"
label={__("Asset Type")} label={__("Asset Type")}
disabled={isSnapshotMode}
> >
<Option value="VIRTUAL">{__("Virtual")}</Option> <Option value="VIRTUAL">{__("Virtual")}</Option>
<Option value="PHYSICAL">{__("Physical")}</Option> <Option value="PHYSICAL">{__("Physical")}</Option>
@@ -153,6 +177,7 @@ export default function AssetDetailsPage(props: Props) {
label={__("Data Types Stored")} label={__("Data Types Stored")}
{...register("dataTypesStored")} {...register("dataTypesStored")}
type="text" type="text"
disabled={isSnapshotMode}
/> />
<PeopleSelectField <PeopleSelectField
@@ -160,6 +185,7 @@ export default function AssetDetailsPage(props: Props) {
control={control} control={control}
name="ownerId" name="ownerId"
label={__("Owner")} label={__("Owner")}
disabled={isSnapshotMode}
/> />
<VendorsMultiSelectField <VendorsMultiSelectField
@@ -167,10 +193,11 @@ export default function AssetDetailsPage(props: Props) {
control={control} control={control}
name="vendorIds" name="vendorIds"
label={__("Vendors")} label={__("Vendors")}
disabled={isSnapshotMode}
/> />
<div className="flex justify-end"> <div className="flex justify-end">
{formState.isDirty && ( {formState.isDirty && !isSnapshotMode && (
<Button type="submit" disabled={formState.isSubmitting}> <Button type="submit" disabled={formState.isSubmitting}>
{formState.isSubmitting ? __("Updating...") : __("Update")} {formState.isSubmitting ? __("Updating...") : __("Update")}
</Button> </Button>

View File

@@ -22,6 +22,7 @@ import {
type PreloadedQuery, type PreloadedQuery,
} from "react-relay"; } from "react-relay";
import { useOrganizationId } from "/hooks/useOrganizationId"; import { useOrganizationId } from "/hooks/useOrganizationId";
import { useParams } from "react-router";
import { CreateAssetDialog } from "./dialogs/CreateAssetDialog"; import { CreateAssetDialog } from "./dialogs/CreateAssetDialog";
import { useDeleteAsset, assetsQuery } from "../../../hooks/graph/AssetGraph"; import { useDeleteAsset, assetsQuery } from "../../../hooks/graph/AssetGraph";
import type { AssetGraphListQuery } from "/hooks/graph/__generated__/AssetGraphListQuery.graphql"; import type { AssetGraphListQuery } from "/hooks/graph/__generated__/AssetGraphListQuery.graphql";
@@ -33,6 +34,7 @@ import type {
AssetsPageFragment$key, AssetsPageFragment$key,
} from "./__generated__/AssetsPageFragment.graphql"; } from "./__generated__/AssetsPageFragment.graphql";
import { SortableTable } from "/components/SortableTable"; import { SortableTable } from "/components/SortableTable";
import { SnapshotBanner } from "/components/SnapshotBanner";
const paginatedAssetsFragment = graphql` const paginatedAssetsFragment = graphql`
fragment AssetsPageFragment on Organization fragment AssetsPageFragment on Organization
@@ -43,6 +45,7 @@ const paginatedAssetsFragment = graphql`
after: { type: "CursorKey", defaultValue: null } after: { type: "CursorKey", defaultValue: null }
before: { type: "CursorKey", defaultValue: null } before: { type: "CursorKey", defaultValue: null }
last: { type: "Int", defaultValue: null } last: { type: "Int", defaultValue: null }
snapshotId: { type: "ID", defaultValue: null }
) { ) {
assets( assets(
first: $first first: $first
@@ -50,11 +53,13 @@ const paginatedAssetsFragment = graphql`
last: $last last: $last
before: $before before: $before
orderBy: $orderBy orderBy: $orderBy
) @connection(key: "AssetsPage_assets") { filter: { snapshotId: $snapshotId }
) @connection(key: "AssetsPage_assets", filters: ["filter"]) {
__id __id
edges { edges {
node { node {
id id
snapshotId
name name
amount amount
criticity criticity
@@ -89,6 +94,8 @@ type Props = {
export default function AssetsPage(props: Props) { export default function AssetsPage(props: Props) {
const { __ } = useTranslate(); const { __ } = useTranslate();
const organizationId = useOrganizationId(); const organizationId = useOrganizationId();
const { snapshotId } = useParams<{ snapshotId?: string }>();
const isSnapshotMode = Boolean(snapshotId);
const data = usePreloadedQuery(assetsQuery, props.queryRef); const data = usePreloadedQuery(assetsQuery, props.queryRef);
const pagination = usePaginationFragment( const pagination = usePaginationFragment(
@@ -102,18 +109,21 @@ export default function AssetsPage(props: Props) {
return ( return (
<div className="space-y-6"> <div className="space-y-6">
{snapshotId && <SnapshotBanner snapshotId={snapshotId} />}
<PageHeader <PageHeader
title={__("Assets")} title={__("Assets")}
description={__( description={__(
"Manage your organization's assets and their classifications." "Manage your organization's assets and their classifications."
)} )}
> >
{!isSnapshotMode && (
<CreateAssetDialog <CreateAssetDialog
connection={connectionId} connection={connectionId}
organizationId={organizationId} organizationId={organizationId}
> >
<Button icon={IconPlusLarge}>{__("Add asset")}</Button> <Button icon={IconPlusLarge}>{__("Add asset")}</Button>
</CreateAssetDialog> </CreateAssetDialog>
)}
</PageHeader> </PageHeader>
<SortableTable {...pagination}> <SortableTable {...pagination}>
<Thead> <Thead>
@@ -150,11 +160,17 @@ function AssetRow({
}) { }) {
const organizationId = useOrganizationId(); const organizationId = useOrganizationId();
const { __ } = useTranslate(); const { __ } = useTranslate();
const { snapshotId } = useParams<{ snapshotId?: string }>();
const isSnapshotMode = Boolean(snapshotId);
const deleteAsset = useDeleteAsset(entry, connectionId); const deleteAsset = useDeleteAsset(entry, connectionId);
const vendors = entry.vendors?.edges.map((edge) => edge.node) ?? []; const vendors = entry.vendors?.edges.map((edge) => edge.node) ?? [];
const assetUrl = isSnapshotMode && snapshotId
? `/organizations/${organizationId}/snapshots/${snapshotId}/assets/${entry.id}`
: `/organizations/${organizationId}/assets/${entry.id}`;
return ( return (
<Tr to={`/organizations/${organizationId}/assets/${entry.id}`}> <Tr to={assetUrl}>
<Td>{entry.name}</Td> <Td>{entry.name}</Td>
<Td> <Td>
<Badge variant={getAssetTypeVariant(entry.assetType)}> <Badge variant={getAssetTypeVariant(entry.assetType)}>
@@ -196,6 +212,7 @@ function AssetRow({
)} )}
</Td> </Td>
<Td noLink width={50} className="text-end"> <Td noLink width={50} className="text-end">
{!isSnapshotMode && (
<ActionDropdown> <ActionDropdown>
<DropdownItem <DropdownItem
onClick={deleteAsset} onClick={deleteAsset}
@@ -205,6 +222,7 @@ function AssetRow({
{__("Delete")} {__("Delete")}
</DropdownItem> </DropdownItem>
</ActionDropdown> </ActionDropdown>
)}
</Td> </Td>
</Tr> </Tr>
); );

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<305c2e15c19e7ead66752260c29d8794>> * @generated SignedSource<<f2135bf85e1d0dffde8c1bcaa7940504>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -23,6 +23,7 @@ export type AssetsListQuery$variables = {
id: string; id: string;
last?: number | null | undefined; last?: number | null | undefined;
orderBy?: AssetOrder | null | undefined; orderBy?: AssetOrder | null | undefined;
snapshotId?: string | null | undefined;
}; };
export type AssetsListQuery$data = { export type AssetsListQuery$data = {
readonly node: { readonly node: {
@@ -65,55 +66,77 @@ v5 = {
"kind": "LocalArgument", "kind": "LocalArgument",
"name": "orderBy" "name": "orderBy"
}, },
v6 = [ v6 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "snapshotId"
},
v7 = [
{ {
"kind": "Variable", "kind": "Variable",
"name": "id", "name": "id",
"variableName": "id" "variableName": "id"
} }
], ],
v7 = [ v8 = {
{
"kind": "Variable", "kind": "Variable",
"name": "after", "name": "after",
"variableName": "after" "variableName": "after"
}, },
{ v9 = {
"kind": "Variable", "kind": "Variable",
"name": "before", "name": "before",
"variableName": "before" "variableName": "before"
}, },
{ v10 = {
"kind": "Variable", "kind": "Variable",
"name": "first", "name": "first",
"variableName": "first" "variableName": "first"
}, },
{ v11 = {
"kind": "Variable", "kind": "Variable",
"name": "last", "name": "last",
"variableName": "last" "variableName": "last"
}, },
{ v12 = {
"kind": "Variable", "kind": "Variable",
"name": "orderBy", "name": "orderBy",
"variableName": "orderBy" "variableName": "orderBy"
} },
], v13 = {
v8 = { "kind": "Variable",
"name": "snapshotId",
"variableName": "snapshotId"
},
v14 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "__typename", "name": "__typename",
"storageKey": null "storageKey": null
}, },
v9 = { v15 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "id", "name": "id",
"storageKey": null "storageKey": null
}, },
v10 = { v16 = [
(v8/*: any*/),
(v9/*: any*/),
{
"fields": [
(v13/*: any*/)
],
"kind": "ObjectValue",
"name": "filter"
},
(v10/*: any*/),
(v11/*: any*/),
(v12/*: any*/)
],
v17 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
@@ -128,7 +151,8 @@ return {
(v2/*: any*/), (v2/*: any*/),
(v3/*: any*/), (v3/*: any*/),
(v4/*: any*/), (v4/*: any*/),
(v5/*: any*/) (v5/*: any*/),
(v6/*: any*/)
], ],
"kind": "Fragment", "kind": "Fragment",
"metadata": null, "metadata": null,
@@ -136,14 +160,21 @@ return {
"selections": [ "selections": [
{ {
"alias": null, "alias": null,
"args": (v6/*: any*/), "args": (v7/*: any*/),
"concreteType": null, "concreteType": null,
"kind": "LinkedField", "kind": "LinkedField",
"name": "node", "name": "node",
"plural": false, "plural": false,
"selections": [ "selections": [
{ {
"args": (v7/*: any*/), "args": [
(v8/*: any*/),
(v9/*: any*/),
(v10/*: any*/),
(v11/*: any*/),
(v12/*: any*/),
(v13/*: any*/)
],
"kind": "FragmentSpread", "kind": "FragmentSpread",
"name": "AssetsPageFragment" "name": "AssetsPageFragment"
} }
@@ -162,6 +193,7 @@ return {
(v2/*: any*/), (v2/*: any*/),
(v4/*: any*/), (v4/*: any*/),
(v5/*: any*/), (v5/*: any*/),
(v6/*: any*/),
(v3/*: any*/) (v3/*: any*/)
], ],
"kind": "Operation", "kind": "Operation",
@@ -169,20 +201,20 @@ return {
"selections": [ "selections": [
{ {
"alias": null, "alias": null,
"args": (v6/*: any*/), "args": (v7/*: any*/),
"concreteType": null, "concreteType": null,
"kind": "LinkedField", "kind": "LinkedField",
"name": "node", "name": "node",
"plural": false, "plural": false,
"selections": [ "selections": [
(v8/*: any*/), (v14/*: any*/),
(v9/*: any*/), (v15/*: any*/),
{ {
"kind": "InlineFragment", "kind": "InlineFragment",
"selections": [ "selections": [
{ {
"alias": null, "alias": null,
"args": (v7/*: any*/), "args": (v16/*: any*/),
"concreteType": "AssetConnection", "concreteType": "AssetConnection",
"kind": "LinkedField", "kind": "LinkedField",
"name": "assets", "name": "assets",
@@ -204,8 +236,15 @@ return {
"name": "node", "name": "node",
"plural": false, "plural": false,
"selections": [ "selections": [
(v9/*: any*/), (v15/*: any*/),
(v10/*: any*/), {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "snapshotId",
"storageKey": null
},
(v17/*: any*/),
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -242,7 +281,7 @@ return {
"name": "owner", "name": "owner",
"plural": false, "plural": false,
"selections": [ "selections": [
(v9/*: any*/), (v15/*: any*/),
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -283,8 +322,8 @@ return {
"name": "node", "name": "node",
"plural": false, "plural": false,
"selections": [ "selections": [
(v9/*: any*/), (v15/*: any*/),
(v10/*: any*/), (v17/*: any*/),
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -308,7 +347,7 @@ return {
"name": "createdAt", "name": "createdAt",
"storageKey": null "storageKey": null
}, },
(v8/*: any*/) (v14/*: any*/)
], ],
"storageKey": null "storageKey": null
}, },
@@ -378,9 +417,9 @@ return {
}, },
{ {
"alias": null, "alias": null,
"args": (v7/*: any*/), "args": (v16/*: any*/),
"filters": [ "filters": [
"orderBy" "filter"
], ],
"handle": "connection", "handle": "connection",
"key": "AssetsPage_assets", "key": "AssetsPage_assets",
@@ -397,16 +436,16 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "d712227ae7c1c1a4151005092f7eeaf9", "cacheID": "2eab5fe4d402f48ac4dcd8220284ea41",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "AssetsListQuery", "name": "AssetsListQuery",
"operationKind": "query", "operationKind": "query",
"text": "query AssetsListQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 10\n $last: Int = null\n $orderBy: AssetOrder = null\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...AssetsPageFragment_sdb03\n id\n }\n}\n\nfragment AssetsPageFragment_sdb03 on Organization {\n assets(first: $first, after: $after, last: $last, before: $before, orderBy: $orderBy) {\n edges {\n node {\n id\n name\n amount\n criticity\n assetType\n dataTypesStored\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 __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n" "text": "query AssetsListQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 10\n $last: Int = null\n $orderBy: AssetOrder = null\n $snapshotId: ID = null\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...AssetsPageFragment_38J9tm\n id\n }\n}\n\nfragment AssetsPageFragment_38J9tm on Organization {\n assets(first: $first, after: $after, last: $last, before: $before, orderBy: $orderBy, filter: {snapshotId: $snapshotId}) {\n edges {\n node {\n id\n snapshotId\n name\n amount\n criticity\n assetType\n dataTypesStored\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 __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 = "e25e58aa9cf789614d280313c6059d5e"; (node as any).hash = "4bc1af55d208f3c773c49b753a76aa5c";
export default node; export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<8bacf647a39e3c33616be7e9159928df>> * @generated SignedSource<<663b14d82ccaeebe2dda8dc256fddfc5>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -28,6 +28,7 @@ export type AssetsPageFragment$data = {
readonly fullName: string; readonly fullName: string;
readonly id: string; readonly id: string;
}; };
readonly snapshotId: string | null | undefined;
readonly vendors: { readonly vendors: {
readonly edges: ReadonlyArray<{ readonly edges: ReadonlyArray<{
readonly node: { readonly node: {
@@ -94,6 +95,11 @@ return {
"defaultValue": null, "defaultValue": null,
"kind": "LocalArgument", "kind": "LocalArgument",
"name": "orderBy" "name": "orderBy"
},
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "snapshotId"
} }
], ],
"kind": "Fragment", "kind": "Fragment",
@@ -133,10 +139,16 @@ return {
{ {
"alias": "assets", "alias": "assets",
"args": [ "args": [
{
"fields": [
{ {
"kind": "Variable", "kind": "Variable",
"name": "orderBy", "name": "snapshotId",
"variableName": "orderBy" "variableName": "snapshotId"
}
],
"kind": "ObjectValue",
"name": "filter"
} }
], ],
"concreteType": "AssetConnection", "concreteType": "AssetConnection",
@@ -161,6 +173,13 @@ return {
"plural": false, "plural": false,
"selections": [ "selections": [
(v1/*: any*/), (v1/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "snapshotId",
"storageKey": null
},
(v2/*: any*/), (v2/*: any*/),
{ {
"alias": null, "alias": null,
@@ -345,6 +364,6 @@ return {
}; };
})(); })();
(node as any).hash = "e25e58aa9cf789614d280313c6059d5e"; (node as any).hash = "4bc1af55d208f3c773c49b753a76aa5c";
export default node; export default node;

View File

@@ -3,13 +3,29 @@ import { relayEnvironment } from "/providers/RelayProviders";
import { PageSkeleton } from "/components/skeletons/PageSkeleton"; import { PageSkeleton } from "/components/skeletons/PageSkeleton";
import { lazy } from "@probo/react-lazy"; import { lazy } from "@probo/react-lazy";
import { assetsQuery, assetNodeQuery } from "../hooks/graph/AssetGraph"; import { assetsQuery, assetNodeQuery } from "../hooks/graph/AssetGraph";
import type { AppRoute } from "/routes";
export const assetRoutes = [ export const assetRoutes = [
{ {
path: "assets", path: "assets",
fallback: PageSkeleton, fallback: PageSkeleton,
queryLoader: (params: Record<string, string>) => queryLoader: (params: Record<string, string>) =>
loadQuery(relayEnvironment, assetsQuery, { organizationId: params.organizationId }), loadQuery(relayEnvironment, assetsQuery, {
organizationId: params.organizationId,
snapshotId: null
}),
Component: lazy(
() => import("/pages/organizations/assets/AssetsPage")
),
},
{
path: "snapshots/:snapshotId/assets",
fallback: PageSkeleton,
queryLoader: (params: Record<string, string>) =>
loadQuery(relayEnvironment, assetsQuery, {
organizationId: params.organizationId,
snapshotId: params.snapshotId
}),
Component: lazy( Component: lazy(
() => import("/pages/organizations/assets/AssetsPage") () => import("/pages/organizations/assets/AssetsPage")
), ),
@@ -23,4 +39,13 @@ export const assetRoutes = [
() => import("/pages/organizations/assets/AssetDetailsPage") () => import("/pages/organizations/assets/AssetDetailsPage")
), ),
}, },
]; {
path: "snapshots/:snapshotId/assets/:assetId",
fallback: PageSkeleton,
queryLoader: (params: Record<string, string>) =>
loadQuery(relayEnvironment, assetNodeQuery, { assetId: params.assetId }),
Component: lazy(
() => import("/pages/organizations/assets/AssetDetailsPage")
),
},
] satisfies AppRoute[];

View File

@@ -1,6 +1,7 @@
type Translator = (s: string) => string; type Translator = (s: string) => string;
export const snapshotTypes = [ export const snapshotTypes = [
"ASSETS",
"DATA", "DATA",
"NONCONFORMITY_REGISTRIES", "NONCONFORMITY_REGISTRIES",
"COMPLIANCE_REGISTRIES", "COMPLIANCE_REGISTRIES",
@@ -37,6 +38,8 @@ export function getSnapshotTypeLabel(__: Translator, type: string | null | undef
export function getSnapshotTypeUrlPath(type?: string): string { export function getSnapshotTypeUrlPath(type?: string): string {
switch (type) { switch (type) {
case "ASSETS":
return "/assets";
case "DATA": case "DATA":
return "/data"; return "/data";
case "NONCONFORMITY_REGISTRIES": case "NONCONFORMITY_REGISTRIES":

View File

@@ -29,6 +29,8 @@ import (
type ( type (
Asset struct { Asset struct {
ID gid.GID `db:"id"` ID gid.GID `db:"id"`
SnapshotID *gid.GID `db:"snapshot_id"`
SourceID *gid.GID `db:"source_id"`
Name string `db:"name"` Name string `db:"name"`
Amount int `db:"amount"` Amount int `db:"amount"`
OwnerID gid.GID `db:"owner_id"` OwnerID gid.GID `db:"owner_id"`
@@ -65,6 +67,8 @@ func (a *Asset) LoadByID(
q := ` q := `
SELECT SELECT
id, id,
snapshot_id,
source_id,
name, name,
organization_id, organization_id,
owner_id, owner_id,
@@ -110,6 +114,8 @@ func (a *Asset) LoadByOwnerID(
q := ` q := `
SELECT SELECT
id, id,
snapshot_id,
source_id,
name, name,
organization_id, organization_id,
owner_id, owner_id,
@@ -152,6 +158,7 @@ func (a *Assets) CountByOrganizationID(
conn pg.Conn, conn pg.Conn,
scope Scoper, scope Scoper,
organizationID gid.GID, organizationID gid.GID,
filter *AssetFilter,
) (int, error) { ) (int, error) {
q := ` q := `
SELECT SELECT
@@ -161,12 +168,14 @@ FROM
WHERE WHERE
%s %s
AND organization_id = @organization_id AND organization_id = @organization_id
AND %s
` `
q = fmt.Sprintf(q, scope.SQLFragment()) q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment())
args := pgx.StrictNamedArgs{"organization_id": organizationID} args := pgx.StrictNamedArgs{"organization_id": organizationID}
maps.Copy(args, scope.SQLArguments()) maps.Copy(args, scope.SQLArguments())
maps.Copy(args, filter.SQLArguments())
row := conn.QueryRow(ctx, q, args) row := conn.QueryRow(ctx, q, args)
@@ -184,10 +193,13 @@ func (a *Assets) LoadByOrganizationID(
scope Scoper, scope Scoper,
organizationID gid.GID, organizationID gid.GID,
cursor *page.Cursor[AssetOrderField], cursor *page.Cursor[AssetOrderField],
filter *AssetFilter,
) error { ) error {
q := ` q := `
SELECT SELECT
id, id,
snapshot_id,
source_id,
name, name,
organization_id, organization_id,
owner_id, owner_id,
@@ -203,12 +215,14 @@ WHERE
%s %s
AND organization_id = @organization_id AND organization_id = @organization_id
AND %s AND %s
AND %s
` `
q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment()) q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment(), cursor.SQLFragment())
args := pgx.StrictNamedArgs{"organization_id": organizationID} args := pgx.StrictNamedArgs{"organization_id": organizationID}
maps.Copy(args, scope.SQLArguments()) maps.Copy(args, scope.SQLArguments())
maps.Copy(args, filter.SQLArguments())
maps.Copy(args, cursor.SQLArguments()) maps.Copy(args, cursor.SQLArguments())
rows, err := conn.Query(ctx, q, args) rows, err := conn.Query(ctx, q, args)
@@ -299,8 +313,11 @@ SET
WHERE WHERE
%s %s
AND id = @id AND id = @id
AND snapshot_id IS NULL
RETURNING RETURNING
id, id,
snapshot_id,
source_id,
name, name,
organization_id, organization_id,
owner_id, owner_id,
@@ -351,6 +368,7 @@ DELETE FROM assets
WHERE WHERE
%s %s
AND id = @id AND id = @id
AND snapshot_id IS NULL
` `
q = fmt.Sprintf(q, scope.SQLFragment()) q = fmt.Sprintf(q, scope.SQLFragment())
@@ -365,3 +383,79 @@ WHERE
return nil return nil
} }
func (assets Assets) Snapshot(ctx context.Context, conn pg.Conn, scope Scoper, organizationID, snapshotID gid.GID) error {
snapshotters := []AssetSnapshotter{Assets{}, Vendors{}, AssetVendors{}}
for _, snapshotter := range snapshotters {
if err := snapshotter.InsertAssetSnapshots(ctx, conn, scope, organizationID, snapshotID); err != nil {
return fmt.Errorf("cannot create asset snapshots: (%T) %w", snapshotter, err)
}
}
return nil
}
func (assets Assets) InsertAssetSnapshots(
ctx context.Context,
conn pg.Conn,
scope Scoper,
organizationID gid.GID,
snapshotID gid.GID,
) error {
query := `
WITH
source_assets AS (
SELECT *
FROM assets
WHERE %s AND organization_id = @organization_id AND snapshot_id IS NULL
)
INSERT INTO assets (
tenant_id,
id,
snapshot_id,
source_id,
name,
organization_id,
owner_id,
amount,
criticity,
asset_type,
data_types_stored,
created_at,
updated_at
)
SELECT
@tenant_id,
generate_gid(decode_base64_unpadded(@tenant_id), @asset_entity_type),
@snapshot_id,
a.id,
a.name,
a.organization_id,
a.owner_id,
a.amount,
a.criticity,
a.asset_type,
a.data_types_stored,
a.created_at,
a.updated_at
FROM source_assets a
`
query = fmt.Sprintf(query, scope.SQLFragment())
args := pgx.StrictNamedArgs{
"tenant_id": scope.GetTenantID(),
"snapshot_id": snapshotID,
"organization_id": organizationID,
"asset_entity_type": AssetEntityType,
}
maps.Copy(args, scope.SQLArguments())
_, err := conn.Exec(ctx, query, args)
if err != nil {
return fmt.Errorf("cannot insert asset snapshots: %w", err)
}
return nil
}

View File

@@ -0,0 +1,54 @@
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package coredata
import (
"github.com/getprobo/probo/pkg/gid"
"github.com/jackc/pgx/v5"
)
type (
AssetFilter struct {
snapshotID **gid.GID
}
)
func NewAssetFilter(snapshotID **gid.GID) *AssetFilter {
return &AssetFilter{
snapshotID: snapshotID,
}
}
func (f *AssetFilter) SQLArguments() pgx.NamedArgs {
args := pgx.NamedArgs{}
if f.snapshotID != nil && *f.snapshotID != nil {
args["filter_snapshot_id"] = **f.snapshotID
}
return args
}
func (f *AssetFilter) SQLFragment() string {
if f.snapshotID == nil {
return "TRUE"
}
if *f.snapshotID == nil {
return "snapshot_id IS NULL"
} else {
return "snapshot_id = @filter_snapshot_id"
}
}

View File

@@ -17,6 +17,7 @@ package coredata
import ( import (
"context" "context"
"fmt" "fmt"
"maps"
"time" "time"
"github.com/getprobo/probo/pkg/gid" "github.com/getprobo/probo/pkg/gid"
@@ -29,10 +30,15 @@ type (
AssetID gid.GID `db:"asset_id"` AssetID gid.GID `db:"asset_id"`
VendorID gid.GID `db:"vendor_id"` VendorID gid.GID `db:"vendor_id"`
TenantID gid.TenantID `db:"tenant_id"` TenantID gid.TenantID `db:"tenant_id"`
SnapshotID *gid.GID `db:"snapshot_id"`
CreatedAt time.Time `db:"created_at"` CreatedAt time.Time `db:"created_at"`
} }
AssetVendors []*AssetVendor AssetVendors []*AssetVendor
AssetSnapshotter interface {
InsertAssetSnapshots(ctx context.Context, conn pg.Conn, scope Scoper, organizationID, snapshotID gid.GID) error
}
) )
func (av AssetVendors) Merge( func (av AssetVendors) Merge(
@@ -112,3 +118,61 @@ FROM vendor_ids
return nil return nil
} }
func (av AssetVendors) InsertAssetSnapshots(
ctx context.Context,
conn pg.Conn,
scope Scoper,
organizationID gid.GID,
snapshotID gid.GID,
) error {
query := `
WITH
source_assets AS (
SELECT id
FROM assets
WHERE organization_id = @organization_id AND snapshot_id IS NULL
),
snapshot_assets AS (
SELECT id, source_id
FROM assets
WHERE organization_id = @organization_id AND snapshot_id = @snapshot_id
),
snapshot_vendors AS (
SELECT id, source_id
FROM vendors
WHERE organization_id = @organization_id AND snapshot_id = @snapshot_id
),
source_asset_vendors AS (
SELECT asset_id, vendor_id, snapshot_id, created_at
FROM asset_vendors
WHERE %s AND asset_id = ANY(SELECT id FROM source_assets) AND snapshot_id IS NULL
)
INSERT INTO asset_vendors (tenant_id, asset_id, vendor_id, snapshot_id, created_at)
SELECT
@tenant_id,
sa.id,
sv.id,
@snapshot_id,
av.created_at
FROM source_asset_vendors av
JOIN snapshot_assets sa ON sa.source_id = av.asset_id
JOIN snapshot_vendors sv ON sv.source_id = av.vendor_id
`
query = fmt.Sprintf(query, scope.SQLFragment())
args := pgx.StrictNamedArgs{
"tenant_id": scope.GetTenantID(),
"snapshot_id": snapshotID,
"organization_id": organizationID,
}
maps.Copy(args, scope.SQLArguments())
_, err := conn.Exec(ctx, query, args)
if err != nil {
return fmt.Errorf("cannot insert asset vendor snapshots: %w", err)
}
return nil
}

View File

@@ -0,0 +1,19 @@
ALTER TABLE assets ADD COLUMN snapshot_id TEXT;
ALTER TABLE assets ADD COLUMN source_id TEXT;
ALTER TABLE assets ADD CONSTRAINT assets_snapshot_id_fkey
FOREIGN KEY (snapshot_id)
REFERENCES snapshots(id)
ON UPDATE CASCADE
ON DELETE CASCADE;
ALTER TABLE assets ADD CONSTRAINT assets_source_id_snapshot_id_key
UNIQUE (source_id, snapshot_id);
ALTER TABLE asset_vendors ADD COLUMN snapshot_id TEXT;
ALTER TABLE asset_vendors ADD CONSTRAINT asset_vendors_snapshot_id_fkey
FOREIGN KEY (snapshot_id)
REFERENCES snapshots(id)
ON UPDATE CASCADE
ON DELETE CASCADE;

View File

@@ -28,6 +28,8 @@ type Snapshottable interface {
func GetSnapshottable(snapshotType SnapshotsType) (Snapshottable, error) { func GetSnapshottable(snapshotType SnapshotsType) (Snapshottable, error) {
switch snapshotType { switch snapshotType {
case SnapshotsTypeAssets:
return Assets{}, nil
case SnapshotsTypeData: case SnapshotsTypeData:
return Data{}, nil return Data{}, nil
case SnapshotsTypeNonConformityRegistries: case SnapshotsTypeNonConformityRegistries:

View File

@@ -814,3 +814,103 @@ FROM source_vendors v
return nil return nil
} }
func (vs Vendors) InsertAssetSnapshots(
ctx context.Context,
conn pg.Conn,
scope Scoper,
organizationID gid.GID,
snapshotID gid.GID,
) error {
query := `
WITH
source_assets AS (
SELECT id
FROM assets
WHERE organization_id = @organization_id AND snapshot_id IS NULL
),
source_asset_vendors AS (
SELECT asset_id, vendor_id, snapshot_id, created_at
FROM asset_vendors
WHERE asset_id = ANY(SELECT id FROM source_assets)
),
source_vendors AS (
SELECT *
FROM vendors
WHERE %s AND id = ANY(SELECT vendor_id FROM source_asset_vendors)
)
INSERT INTO vendors (
tenant_id,
id,
snapshot_id,
source_id,
organization_id,
name,
description,
category,
headquarter_address,
legal_name,
website_url,
privacy_policy_url,
service_level_agreement_url,
data_processing_agreement_url,
business_associate_agreement_url,
subprocessors_list_url,
certifications,
business_owner_id,
security_owner_id,
status_page_url,
terms_of_service_url,
security_page_url,
trust_page_url,
show_on_trust_center,
created_at,
updated_at
)
SELECT
@tenant_id,
generate_gid(decode_base64_unpadded(@tenant_id), @vendor_entity_type),
@snapshot_id,
v.id,
v.organization_id,
v.name,
v.description,
v.category,
v.headquarter_address,
v.legal_name,
v.website_url,
v.privacy_policy_url,
v.service_level_agreement_url,
v.data_processing_agreement_url,
v.business_associate_agreement_url,
v.subprocessors_list_url,
v.certifications,
v.business_owner_id,
v.security_owner_id,
v.status_page_url,
v.terms_of_service_url,
v.security_page_url,
v.trust_page_url,
v.show_on_trust_center,
v.created_at,
v.updated_at
FROM source_vendors v
`
query = fmt.Sprintf(query, scope.SQLFragment())
args := pgx.StrictNamedArgs{
"tenant_id": scope.GetTenantID(),
"snapshot_id": snapshotID,
"organization_id": organizationID,
"vendor_entity_type": VendorEntityType,
}
maps.Copy(args, scope.SQLArguments())
_, err := conn.Exec(ctx, query, args)
if err != nil {
return fmt.Errorf("cannot insert vendor snapshots for assets: %w", err)
}
return nil
}

View File

@@ -80,6 +80,7 @@ func (s AssetService) GetByOwnerID(
func (s AssetService) CountForOrganizationID( func (s AssetService) CountForOrganizationID(
ctx context.Context, ctx context.Context,
organizationID gid.GID, organizationID gid.GID,
filter *coredata.AssetFilter,
) (int, error) { ) (int, error) {
var count int var count int
@@ -87,7 +88,7 @@ func (s AssetService) CountForOrganizationID(
ctx, ctx,
func(conn pg.Conn) (err error) { func(conn pg.Conn) (err error) {
assets := coredata.Assets{} assets := coredata.Assets{}
count, err = assets.CountByOrganizationID(ctx, conn, s.svc.scope, organizationID) count, err = assets.CountByOrganizationID(ctx, conn, s.svc.scope, organizationID, filter)
if err != nil { if err != nil {
return fmt.Errorf("cannot count assets: %w", err) return fmt.Errorf("cannot count assets: %w", err)
} }
@@ -107,6 +108,7 @@ func (s AssetService) ListForOrganizationID(
ctx context.Context, ctx context.Context,
organizationID gid.GID, organizationID gid.GID,
cursor *page.Cursor[coredata.AssetOrderField], cursor *page.Cursor[coredata.AssetOrderField],
filter *coredata.AssetFilter,
) (*page.Page[*coredata.Asset, coredata.AssetOrderField], error) { ) (*page.Page[*coredata.Asset, coredata.AssetOrderField], error) {
var assets coredata.Assets var assets coredata.Assets
@@ -119,6 +121,7 @@ func (s AssetService) ListForOrganizationID(
s.svc.scope, s.svc.scope,
organizationID, organizationID,
cursor, cursor,
filter,
) )
}, },
) )

View File

@@ -1130,6 +1130,10 @@ input ProcessingActivityRegistryFilter {
snapshotId: ID snapshotId: ID
} }
input AssetFilter {
snapshotId: ID
}
# Core Types # Core Types
type TrustCenter implements Node { type TrustCenter implements Node {
id: ID! id: ID!
@@ -1244,6 +1248,7 @@ type Organization implements Node {
last: Int last: Int
before: CursorKey before: CursorKey
orderBy: AssetOrder orderBy: AssetOrder
filter: AssetFilter
): AssetConnection! @goField(forceResolver: true) ): AssetConnection! @goField(forceResolver: true)
data( data(
@@ -3639,6 +3644,7 @@ type AssessVendorPayload {
type Asset implements Node { type Asset implements Node {
id: ID! id: ID!
snapshotId: ID
name: String! name: String!
amount: Int! amount: Int!
owner: People! @goField(forceResolver: true) owner: People! @goField(forceResolver: true)

View File

@@ -112,6 +112,7 @@ type ComplexityRoot struct {
Name func(childComplexity int) int Name func(childComplexity int) int
Organization func(childComplexity int) int Organization func(childComplexity int) int
Owner func(childComplexity int) int Owner func(childComplexity int) int
SnapshotID func(childComplexity int) int
UpdatedAt 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 Vendors func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.VendorOrderBy) int
} }
@@ -873,7 +874,7 @@ type ComplexityRoot struct {
} }
Organization struct { Organization struct {
Assets func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AssetOrderBy) int Assets func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AssetOrderBy, filter *types.AssetFilter) int
Audits func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AuditOrderBy) int Audits func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AuditOrderBy) int
ComplianceRegistries func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ComplianceRegistryOrderBy, filter *types.ComplianceRegistryFilter) int ComplianceRegistries func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ComplianceRegistryOrderBy, filter *types.ComplianceRegistryFilter) int
Connectors func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ConnectorOrder) int Connectors func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ConnectorOrder) int
@@ -1671,7 +1672,7 @@ type OrganizationResolver interface {
Measures(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MeasureOrderBy, filter *types.MeasureFilter) (*types.MeasureConnection, error) Measures(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MeasureOrderBy, filter *types.MeasureFilter) (*types.MeasureConnection, error)
Risks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.RiskOrderBy, filter *types.RiskFilter) (*types.RiskConnection, error) Risks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.RiskOrderBy, filter *types.RiskFilter) (*types.RiskConnection, error)
Tasks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.TaskOrderBy) (*types.TaskConnection, error) Tasks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.TaskOrderBy) (*types.TaskConnection, error)
Assets(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AssetOrderBy) (*types.AssetConnection, error) Assets(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AssetOrderBy, filter *types.AssetFilter) (*types.AssetConnection, error)
Data(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DatumOrderBy, filter *types.DatumFilter) (*types.DatumConnection, error) Data(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DatumOrderBy, filter *types.DatumFilter) (*types.DatumConnection, error)
Audits(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AuditOrderBy) (*types.AuditConnection, error) Audits(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AuditOrderBy) (*types.AuditConnection, error)
NonconformityRegistries(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.NonconformityRegistryOrderBy, filter *types.NonconformityRegistryFilter) (*types.NonconformityRegistryConnection, error) NonconformityRegistries(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.NonconformityRegistryOrderBy, filter *types.NonconformityRegistryFilter) (*types.NonconformityRegistryConnection, error)
@@ -1864,6 +1865,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
return e.complexity.Asset.Owner(childComplexity), true return e.complexity.Asset.Owner(childComplexity), true
case "Asset.snapshotId":
if e.complexity.Asset.SnapshotID == nil {
break
}
return e.complexity.Asset.SnapshotID(childComplexity), true
case "Asset.updatedAt": case "Asset.updatedAt":
if e.complexity.Asset.UpdatedAt == nil { if e.complexity.Asset.UpdatedAt == nil {
break break
@@ -5380,7 +5388,7 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
return 0, false return 0, false
} }
return e.complexity.Organization.Assets(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey), args["orderBy"].(*types.AssetOrderBy)), true return e.complexity.Organization.Assets(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey), args["orderBy"].(*types.AssetOrderBy), args["filter"].(*types.AssetFilter)), true
case "Organization.audits": case "Organization.audits":
if e.complexity.Organization.Audits == nil { if e.complexity.Organization.Audits == nil {
@@ -7697,6 +7705,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
ec := executionContext{opCtx, e, 0, 0, make(chan graphql.DeferredResult)} ec := executionContext{opCtx, e, 0, 0, make(chan graphql.DeferredResult)}
inputUnmarshalMap := graphql.BuildUnmarshalerMap( inputUnmarshalMap := graphql.BuildUnmarshalerMap(
ec.unmarshalInputAssessVendorInput, ec.unmarshalInputAssessVendorInput,
ec.unmarshalInputAssetFilter,
ec.unmarshalInputAssetOrder, ec.unmarshalInputAssetOrder,
ec.unmarshalInputAssignTaskInput, ec.unmarshalInputAssignTaskInput,
ec.unmarshalInputAuditOrder, ec.unmarshalInputAuditOrder,
@@ -9073,6 +9082,10 @@ input ProcessingActivityRegistryFilter {
snapshotId: ID snapshotId: ID
} }
input AssetFilter {
snapshotId: ID
}
# Core Types # Core Types
type TrustCenter implements Node { type TrustCenter implements Node {
id: ID! id: ID!
@@ -9187,6 +9200,7 @@ type Organization implements Node {
last: Int last: Int
before: CursorKey before: CursorKey
orderBy: AssetOrder orderBy: AssetOrder
filter: AssetFilter
): AssetConnection! @goField(forceResolver: true) ): AssetConnection! @goField(forceResolver: true)
data( data(
@@ -11582,6 +11596,7 @@ type AssessVendorPayload {
type Asset implements Node { type Asset implements Node {
id: ID! id: ID!
snapshotId: ID
name: String! name: String!
amount: Int! amount: Int!
owner: People! @goField(forceResolver: true) owner: People! @goField(forceResolver: true)
@@ -15883,6 +15898,11 @@ func (ec *executionContext) field_Organization_assets_args(ctx context.Context,
return nil, err return nil, err
} }
args["orderBy"] = arg4 args["orderBy"] = arg4
arg5, err := ec.field_Organization_assets_argsFilter(ctx, rawArgs)
if err != nil {
return nil, err
}
args["filter"] = arg5
return args, nil return args, nil
} }
func (ec *executionContext) field_Organization_assets_argsFirst( func (ec *executionContext) field_Organization_assets_argsFirst(
@@ -15950,6 +15970,19 @@ func (ec *executionContext) field_Organization_assets_argsOrderBy(
return zeroVal, nil return zeroVal, nil
} }
func (ec *executionContext) field_Organization_assets_argsFilter(
ctx context.Context,
rawArgs map[string]any,
) (*types.AssetFilter, error) {
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("filter"))
if tmp, ok := rawArgs["filter"]; ok {
return ec.unmarshalOAssetFilter2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐAssetFilter(ctx, tmp)
}
var zeroVal *types.AssetFilter
return zeroVal, nil
}
func (ec *executionContext) field_Organization_audits_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { func (ec *executionContext) field_Organization_audits_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error var err error
args := map[string]any{} args := map[string]any{}
@@ -19197,6 +19230,47 @@ func (ec *executionContext) fieldContext_Asset_id(_ context.Context, field graph
return fc, nil return fc, nil
} }
func (ec *executionContext) _Asset_snapshotId(ctx context.Context, field graphql.CollectedField, obj *types.Asset) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Asset_snapshotId(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
ctx = rctx // use context from middleware stack in children
return obj.SnapshotID, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*gid.GID)
fc.Result = res
return ec.marshalOID2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGID(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_Asset_snapshotId(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Asset",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type ID does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _Asset_name(ctx context.Context, field graphql.CollectedField, obj *types.Asset) (ret graphql.Marshaler) { func (ec *executionContext) _Asset_name(ctx context.Context, field graphql.CollectedField, obj *types.Asset) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Asset_name(ctx, field) fc, err := ec.fieldContext_Asset_name(ctx, field)
if err != nil { if err != nil {
@@ -19961,6 +20035,8 @@ func (ec *executionContext) fieldContext_AssetEdge_node(_ context.Context, field
switch field.Name { switch field.Name {
case "id": case "id":
return ec.fieldContext_Asset_id(ctx, field) return ec.fieldContext_Asset_id(ctx, field)
case "snapshotId":
return ec.fieldContext_Asset_snapshotId(ctx, field)
case "name": case "name":
return ec.fieldContext_Asset_name(ctx, field) return ec.fieldContext_Asset_name(ctx, field)
case "amount": case "amount":
@@ -41999,7 +42075,7 @@ func (ec *executionContext) _Organization_assets(ctx context.Context, field grap
}() }()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
ctx = rctx // use context from middleware stack in children ctx = rctx // use context from middleware stack in children
return ec.resolvers.Organization().Assets(rctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*page.CursorKey), fc.Args["last"].(*int), fc.Args["before"].(*page.CursorKey), fc.Args["orderBy"].(*types.AssetOrderBy)) return ec.resolvers.Organization().Assets(rctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*page.CursorKey), fc.Args["last"].(*int), fc.Args["before"].(*page.CursorKey), fc.Args["orderBy"].(*types.AssetOrderBy), fc.Args["filter"].(*types.AssetFilter))
}) })
if err != nil { if err != nil {
ec.Error(ctx, err) ec.Error(ctx, err)
@@ -49882,6 +49958,8 @@ func (ec *executionContext) fieldContext_UpdateAssetPayload_asset(_ context.Cont
switch field.Name { switch field.Name {
case "id": case "id":
return ec.fieldContext_Asset_id(ctx, field) return ec.fieldContext_Asset_id(ctx, field)
case "snapshotId":
return ec.fieldContext_Asset_snapshotId(ctx, field)
case "name": case "name":
return ec.fieldContext_Asset_name(ctx, field) return ec.fieldContext_Asset_name(ctx, field)
case "amount": case "amount":
@@ -59641,6 +59719,33 @@ func (ec *executionContext) unmarshalInputAssessVendorInput(ctx context.Context,
return it, nil return it, nil
} }
func (ec *executionContext) unmarshalInputAssetFilter(ctx context.Context, obj any) (types.AssetFilter, error) {
var it types.AssetFilter
asMap := map[string]any{}
for k, v := range obj.(map[string]any) {
asMap[k] = v
}
fieldsInOrder := [...]string{"snapshotId"}
for _, k := range fieldsInOrder {
v, ok := asMap[k]
if !ok {
continue
}
switch k {
case "snapshotId":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("snapshotId"))
data, err := ec.unmarshalOID2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGID(ctx, v)
if err != nil {
return it, err
}
it.SnapshotID = data
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputAssetOrder(ctx context.Context, obj any) (types.AssetOrderBy, error) { func (ec *executionContext) unmarshalInputAssetOrder(ctx context.Context, obj any) (types.AssetOrderBy, error) {
var it types.AssetOrderBy var it types.AssetOrderBy
asMap := map[string]any{} asMap := map[string]any{}
@@ -66297,6 +66402,8 @@ func (ec *executionContext) _Asset(ctx context.Context, sel ast.SelectionSet, ob
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
atomic.AddUint32(&out.Invalids, 1) atomic.AddUint32(&out.Invalids, 1)
} }
case "snapshotId":
out.Values[i] = ec._Asset_snapshotId(ctx, field, obj)
case "name": case "name":
out.Values[i] = ec._Asset_name(ctx, field, obj) out.Values[i] = ec._Asset_name(ctx, field, obj)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
@@ -87081,6 +87188,14 @@ func (ec *executionContext) marshalN__TypeKind2string(ctx context.Context, sel a
return res return res
} }
func (ec *executionContext) unmarshalOAssetFilter2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐAssetFilter(ctx context.Context, v any) (*types.AssetFilter, error) {
if v == nil {
return nil, nil
}
res, err := ec.unmarshalInputAssetFilter(ctx, v)
return &res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) unmarshalOAssetOrder2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐAssetOrderBy(ctx context.Context, v any) (*types.AssetOrderBy, error) { func (ec *executionContext) unmarshalOAssetOrder2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐAssetOrderBy(ctx context.Context, v any) (*types.AssetOrderBy, error) {
if v == nil { if v == nil {
return nil, nil return nil, nil

View File

@@ -16,6 +16,7 @@ type (
Resolver any Resolver any
ParentID gid.GID ParentID gid.GID
Filter *AssetFilter
} }
) )
@@ -23,6 +24,7 @@ func NewAssetConnection(
p *page.Page[*coredata.Asset, coredata.AssetOrderField], p *page.Page[*coredata.Asset, coredata.AssetOrderField],
resolver any, resolver any,
parentID gid.GID, parentID gid.GID,
filter *AssetFilter,
) *AssetConnection { ) *AssetConnection {
edges := make([]*AssetEdge, len(p.Data)) edges := make([]*AssetEdge, len(p.Data))
for i, asset := range p.Data { for i, asset := range p.Data {
@@ -35,12 +37,14 @@ func NewAssetConnection(
Resolver: resolver, Resolver: resolver,
ParentID: parentID, ParentID: parentID,
Filter: filter,
} }
} }
func NewAsset(asset *coredata.Asset) *Asset { func NewAsset(asset *coredata.Asset) *Asset {
return &Asset{ return &Asset{
ID: asset.ID, ID: asset.ID,
SnapshotID: asset.SnapshotID,
Name: asset.Name, Name: asset.Name,
Amount: asset.Amount, Amount: asset.Amount,
Criticity: asset.Criticity, Criticity: asset.Criticity,

View File

@@ -27,6 +27,7 @@ type AssessVendorPayload struct {
type Asset struct { type Asset struct {
ID gid.GID `json:"id"` ID gid.GID `json:"id"`
SnapshotID *gid.GID `json:"snapshotId,omitempty"`
Name string `json:"name"` Name string `json:"name"`
Amount int `json:"amount"` Amount int `json:"amount"`
Owner *People `json:"owner"` Owner *People `json:"owner"`
@@ -47,6 +48,10 @@ type AssetEdge struct {
Node *Asset `json:"node"` Node *Asset `json:"node"`
} }
type AssetFilter struct {
SnapshotID *gid.GID `json:"snapshotId,omitempty"`
}
type AssignTaskInput struct { type AssignTaskInput struct {
TaskID gid.GID `json:"taskId"` TaskID gid.GID `json:"taskId"`
AssignedToID gid.GID `json:"assignedToId"` AssignedToID gid.GID `json:"assignedToId"`

View File

@@ -98,7 +98,12 @@ func (r *assetConnectionResolver) TotalCount(ctx context.Context, obj *types.Ass
switch obj.Resolver.(type) { switch obj.Resolver.(type) {
case *organizationResolver: case *organizationResolver:
count, err := prb.Assets.CountForOrganizationID(ctx, obj.ParentID) assetFilter := coredata.NewAssetFilter(nil)
if obj.Filter != nil {
assetFilter = coredata.NewAssetFilter(&obj.Filter.SnapshotID)
}
count, err := prb.Assets.CountForOrganizationID(ctx, obj.ParentID, assetFilter)
if err != nil { if err != nil {
return 0, fmt.Errorf("cannot count assets: %w", err) return 0, fmt.Errorf("cannot count assets: %w", err)
} }
@@ -3626,7 +3631,7 @@ func (r *organizationResolver) Tasks(ctx context.Context, obj *types.Organizatio
} }
// Assets is the resolver for the assets field. // Assets is the resolver for the assets field.
func (r *organizationResolver) Assets(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AssetOrderBy) (*types.AssetConnection, error) { func (r *organizationResolver) Assets(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AssetOrderBy, filter *types.AssetFilter) (*types.AssetConnection, error) {
prb := r.ProboService(ctx, obj.ID.TenantID()) prb := r.ProboService(ctx, obj.ID.TenantID())
pageOrderBy := page.OrderBy[coredata.AssetOrderField]{ pageOrderBy := page.OrderBy[coredata.AssetOrderField]{
@@ -3642,12 +3647,17 @@ func (r *organizationResolver) Assets(ctx context.Context, obj *types.Organizati
cursor := types.NewCursor(first, after, last, before, pageOrderBy) cursor := types.NewCursor(first, after, last, before, pageOrderBy)
page, err := prb.Assets.ListForOrganizationID(ctx, obj.ID, cursor) assetFilter := coredata.NewAssetFilter(nil)
if filter != nil {
assetFilter = coredata.NewAssetFilter(&filter.SnapshotID)
}
page, err := prb.Assets.ListForOrganizationID(ctx, obj.ID, cursor, assetFilter)
if err != nil { if err != nil {
panic(fmt.Errorf("cannot list organization assets: %w", err)) panic(fmt.Errorf("cannot list organization assets: %w", err))
} }
return types.NewAssetConnection(page, r, obj.ID), nil return types.NewAssetConnection(page, r, obj.ID, filter), nil
} }
// Assets is the resolver for the assets field. // Assets is the resolver for the assets field.

View File

@@ -64,7 +64,9 @@ func (s VendorService) ListForOrganizationId(
ctx, ctx,
func(conn pg.Conn) error { func(conn pg.Conn) error {
showOnTrustCenter := true showOnTrustCenter := true
filter := coredata.NewVendorFilter(nil, &showOnTrustCenter) var nilSnapshotID *gid.GID = nil
filter := coredata.NewVendorFilter(&nilSnapshotID, &showOnTrustCenter)
err := vendors.LoadByOrganizationID(ctx, conn, s.svc.scope, organizationID, cursor, filter) err := vendors.LoadByOrganizationID(ctx, conn, s.svc.scope, organizationID, cursor, filter)
if err != nil { if err != nil {
return fmt.Errorf("cannot load vendors: %w", err) return fmt.Errorf("cannot load vendors: %w", err)