Add social links

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2026-03-09 09:28:12 +01:00
parent 87415c0324
commit fe241fc136
42 changed files with 5428 additions and 415 deletions

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<6ae7a5c203ff61690408ec2753fe2f11>>
* @generated SignedSource<<bd3459ba12fdc1d268c2287e61b17190>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -9,6 +9,7 @@
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type CompliancePageBrandPageQuery$variables = {
organizationId: string;
};
@@ -20,6 +21,7 @@ export type CompliancePageBrandPageQuery$data = {
readonly darkLogoFileUrl: string | null | undefined;
readonly id: string;
readonly logoFileUrl: string | null | undefined;
readonly " $fragmentSpreads": FragmentRefs<"CompliancePageExternalUrlsSection_trustCenterFragment" | "CompliancePageFrameworkList_compliancePageFragment">;
};
} | {
// This will never be '%other', but we need some
@@ -62,44 +64,108 @@ v3 = {
"storageKey": null
},
v4 = {
"alias": "compliancePage",
"alias": null,
"args": null,
"concreteType": "TrustCenter",
"kind": "ScalarField",
"name": "logoFileUrl",
"storageKey": null
},
v5 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "darkLogoFileUrl",
"storageKey": null
},
v6 = {
"alias": "canUpdate",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:trust-center:update"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:trust-center:update\")"
},
v7 = [
{
"kind": "Literal",
"name": "first",
"value": 100
},
{
"kind": "Literal",
"name": "orderBy",
"value": {
"direction": "ASC",
"field": "RANK"
}
}
],
v8 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "rank",
"storageKey": null
},
v9 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
v10 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
},
v11 = {
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "trustCenter",
"name": "pageInfo",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "logoFileUrl",
"name": "endCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "darkLogoFileUrl",
"name": "hasNextPage",
"storageKey": null
},
{
"alias": "canUpdate",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:trust-center:update"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:trust-center:update\")"
}
],
"storageKey": null
};
},
v12 = {
"kind": "ClientExtension",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__id",
"storageKey": null
}
]
},
v13 = [
"orderBy"
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
@@ -121,7 +187,31 @@ return {
"selections": [
{
"kind": "RequiredField",
"field": (v4/*: any*/),
"field": {
"alias": "compliancePage",
"args": null,
"concreteType": "TrustCenter",
"kind": "LinkedField",
"name": "trustCenter",
"plural": false,
"selections": [
(v3/*: any*/),
(v4/*: any*/),
(v5/*: any*/),
(v6/*: any*/),
{
"args": null,
"kind": "FragmentSpread",
"name": "CompliancePageFrameworkList_compliancePageFragment"
},
{
"args": null,
"kind": "FragmentSpread",
"name": "CompliancePageExternalUrlsSection_trustCenterFragment"
}
],
"storageKey": null
},
"action": "THROW"
}
],
@@ -153,7 +243,159 @@ return {
{
"kind": "InlineFragment",
"selections": [
(v4/*: any*/)
{
"alias": "compliancePage",
"args": null,
"concreteType": "TrustCenter",
"kind": "LinkedField",
"name": "trustCenter",
"plural": false,
"selections": [
(v3/*: any*/),
(v4/*: any*/),
(v5/*: any*/),
(v6/*: any*/),
{
"alias": null,
"args": (v7/*: any*/),
"concreteType": "ComplianceFrameworkConnection",
"kind": "LinkedField",
"name": "complianceFrameworks",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "ComplianceFrameworkEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "ComplianceFramework",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v3/*: any*/),
(v8/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "visibility",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "Framework",
"kind": "LinkedField",
"name": "framework",
"plural": false,
"selections": [
(v3/*: any*/),
(v9/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "lightLogoURL",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "darkLogoURL",
"storageKey": null
}
],
"storageKey": null
},
(v2/*: any*/)
],
"storageKey": null
},
(v10/*: any*/)
],
"storageKey": null
},
(v11/*: any*/),
(v12/*: any*/)
],
"storageKey": "complianceFrameworks(first:100,orderBy:{\"direction\":\"ASC\",\"field\":\"RANK\"})"
},
{
"alias": null,
"args": (v7/*: any*/),
"filters": (v13/*: any*/),
"handle": "connection",
"key": "CompliancePageFrameworkList_complianceFrameworks",
"kind": "LinkedHandle",
"name": "complianceFrameworks"
},
{
"alias": null,
"args": (v7/*: any*/),
"concreteType": "ComplianceExternalURLConnection",
"kind": "LinkedField",
"name": "externalUrls",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "ComplianceExternalURLEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "ComplianceExternalURL",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v3/*: any*/),
(v9/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "url",
"storageKey": null
},
(v8/*: any*/),
(v2/*: any*/)
],
"storageKey": null
},
(v10/*: any*/)
],
"storageKey": null
},
(v11/*: any*/),
(v12/*: any*/)
],
"storageKey": "externalUrls(first:100,orderBy:{\"direction\":\"ASC\",\"field\":\"RANK\"})"
},
{
"alias": null,
"args": (v7/*: any*/),
"filters": (v13/*: any*/),
"handle": "connection",
"key": "CompliancePageExternalUrlsSection_externalUrls",
"kind": "LinkedHandle",
"name": "externalUrls"
}
],
"storageKey": null
}
],
"type": "Organization",
"abstractKey": null
@@ -165,16 +407,16 @@ return {
]
},
"params": {
"cacheID": "a0e24c6bb927ff34274492952df0346b",
"cacheID": "8a6a8094cbc0edebe011b284ac9638dc",
"id": null,
"metadata": {},
"name": "CompliancePageBrandPageQuery",
"operationKind": "query",
"text": "query CompliancePageBrandPageQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n compliancePage: trustCenter {\n id\n logoFileUrl\n darkLogoFileUrl\n canUpdate: permission(action: \"core:trust-center:update\")\n }\n }\n id\n }\n}\n"
"text": "query CompliancePageBrandPageQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n compliancePage: trustCenter {\n id\n logoFileUrl\n darkLogoFileUrl\n canUpdate: permission(action: \"core:trust-center:update\")\n ...CompliancePageFrameworkList_compliancePageFragment\n ...CompliancePageExternalUrlsSection_trustCenterFragment\n }\n }\n id\n }\n}\n\nfragment CompliancePageExternalUrlsSection_trustCenterFragment on TrustCenter {\n id\n canUpdate: permission(action: \"core:trust-center:update\")\n externalUrls(first: 100, orderBy: {field: RANK, direction: ASC}) {\n edges {\n node {\n id\n name\n url\n rank\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n\nfragment CompliancePageFrameworkList_compliancePageFragment on TrustCenter {\n id\n canUpdate: permission(action: \"core:trust-center:update\")\n complianceFrameworks(first: 100, orderBy: {field: RANK, direction: ASC}) {\n edges {\n node {\n id\n rank\n visibility\n framework {\n id\n name\n lightLogoURL\n darkLogoURL\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n"
}
};
})();
(node as any).hash = "3d2d6ef82bb9cf232aad738c906b8a54";
(node as any).hash = "d0a4169ebdc6d099e49faa2839d6f33d";
export default node;

View File

@@ -0,0 +1,144 @@
/**
* @generated SignedSource<<23a875a3299a51bdebef4d121b0b84ff>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type CreateComplianceExternalURLInput = {
name: string;
trustCenterId: string;
url: string;
};
export type CompliancePageExternalUrlsSection_createMutation$variables = {
input: CreateComplianceExternalURLInput;
};
export type CompliancePageExternalUrlsSection_createMutation$data = {
readonly createComplianceExternalURL: {
readonly complianceExternalUrlEdge: {
readonly node: {
readonly id: string;
readonly name: string;
readonly rank: number;
readonly url: string;
};
};
};
};
export type CompliancePageExternalUrlsSection_createMutation = {
response: CompliancePageExternalUrlsSection_createMutation$data;
variables: CompliancePageExternalUrlsSection_createMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
}
],
v1 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
"concreteType": "CreateComplianceExternalURLPayload",
"kind": "LinkedField",
"name": "createComplianceExternalURL",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "ComplianceExternalURLEdge",
"kind": "LinkedField",
"name": "complianceExternalUrlEdge",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "ComplianceExternalURL",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "url",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "rank",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "CompliancePageExternalUrlsSection_createMutation",
"selections": (v1/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "CompliancePageExternalUrlsSection_createMutation",
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "946285e4ff57ca46030c3c2ec5b8cc22",
"id": null,
"metadata": {},
"name": "CompliancePageExternalUrlsSection_createMutation",
"operationKind": "mutation",
"text": "mutation CompliancePageExternalUrlsSection_createMutation(\n $input: CreateComplianceExternalURLInput!\n) {\n createComplianceExternalURL(input: $input) {\n complianceExternalUrlEdge {\n node {\n id\n name\n url\n rank\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "8441156775b5070a2dc3f0e316d5bb0b";
export default node;

View File

@@ -0,0 +1,92 @@
/**
* @generated SignedSource<<b014bdb0fa2614aa7df1efb00c5efb3a>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type DeleteComplianceExternalURLInput = {
id: string;
};
export type CompliancePageExternalUrlsSection_deleteMutation$variables = {
input: DeleteComplianceExternalURLInput;
};
export type CompliancePageExternalUrlsSection_deleteMutation$data = {
readonly deleteComplianceExternalURL: {
readonly deletedComplianceExternalUrlId: string;
};
};
export type CompliancePageExternalUrlsSection_deleteMutation = {
response: CompliancePageExternalUrlsSection_deleteMutation$data;
variables: CompliancePageExternalUrlsSection_deleteMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
}
],
v1 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
"concreteType": "DeleteComplianceExternalURLPayload",
"kind": "LinkedField",
"name": "deleteComplianceExternalURL",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "deletedComplianceExternalUrlId",
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "CompliancePageExternalUrlsSection_deleteMutation",
"selections": (v1/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "CompliancePageExternalUrlsSection_deleteMutation",
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "e0715ce1d37b8854b0eeea3e6214973c",
"id": null,
"metadata": {},
"name": "CompliancePageExternalUrlsSection_deleteMutation",
"operationKind": "mutation",
"text": "mutation CompliancePageExternalUrlsSection_deleteMutation(\n $input: DeleteComplianceExternalURLInput!\n) {\n deleteComplianceExternalURL(input: $input) {\n deletedComplianceExternalUrlId\n }\n}\n"
}
};
})();
(node as any).hash = "394380c11f7ee240360143f8f71f058c";
export default node;

View File

@@ -0,0 +1,233 @@
/**
* @generated SignedSource<<b1de38a260511be046ecf7c640f9474b>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ReaderFragment } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type CompliancePageExternalUrlsSection_trustCenterFragment$data = {
readonly canUpdate: boolean;
readonly externalUrls: {
readonly __id: string;
readonly edges: ReadonlyArray<{
readonly node: {
readonly id: string;
readonly name: string;
readonly rank: number;
readonly url: string;
};
}>;
};
readonly id: string;
readonly " $fragmentType": "CompliancePageExternalUrlsSection_trustCenterFragment";
};
export type CompliancePageExternalUrlsSection_trustCenterFragment$key = {
readonly " $data"?: CompliancePageExternalUrlsSection_trustCenterFragment$data;
readonly " $fragmentSpreads": FragmentRefs<"CompliancePageExternalUrlsSection_trustCenterFragment">;
};
import CompliancePageExternalUrlsSection_trustCenterRefetchQuery_graphql from './CompliancePageExternalUrlsSection_trustCenterRefetchQuery.graphql';
const node: ReaderFragment = (function(){
var v0 = [
"externalUrls"
],
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
};
return {
"argumentDefinitions": [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "after"
},
{
"defaultValue": 100,
"kind": "LocalArgument",
"name": "first"
},
{
"defaultValue": {
"direction": "ASC",
"field": "RANK"
},
"kind": "LocalArgument",
"name": "order"
}
],
"kind": "Fragment",
"metadata": {
"connection": [
{
"count": "first",
"cursor": "after",
"direction": "forward",
"path": (v0/*: any*/)
}
],
"refetch": {
"connection": {
"forward": {
"count": "first",
"cursor": "after"
},
"backward": null,
"path": (v0/*: any*/)
},
"fragmentPathInResult": [
"node"
],
"operation": CompliancePageExternalUrlsSection_trustCenterRefetchQuery_graphql,
"identifierInfo": {
"identifierField": "id",
"identifierQueryVariableName": "id"
}
}
},
"name": "CompliancePageExternalUrlsSection_trustCenterFragment",
"selections": [
(v1/*: any*/),
{
"alias": "canUpdate",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:trust-center:update"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:trust-center:update\")"
},
{
"alias": "externalUrls",
"args": [
{
"kind": "Variable",
"name": "orderBy",
"variableName": "order"
}
],
"concreteType": "ComplianceExternalURLConnection",
"kind": "LinkedField",
"name": "__CompliancePageExternalUrlsSection_externalUrls_connection",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "ComplianceExternalURLEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "ComplianceExternalURL",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v1/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "url",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "rank",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
}
],
"storageKey": null
},
{
"kind": "ClientExtension",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__id",
"storageKey": null
}
]
}
],
"storageKey": null
}
],
"type": "TrustCenter",
"abstractKey": null
};
})();
(node as any).hash = "f2614c45eaf65a9d6c6f857abc7c66f6";
export default node;

View File

@@ -0,0 +1,309 @@
/**
* @generated SignedSource<<1fdf3d464ce9b10a9dd2ea55a9dbfc26>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type ComplianceExternalURLOrderField = "CREATED_AT" | "RANK";
export type OrderDirection = "ASC" | "DESC";
export type ComplianceExternalURLOrder = {
direction: OrderDirection;
field: ComplianceExternalURLOrderField;
};
export type CompliancePageExternalUrlsSection_trustCenterRefetchQuery$variables = {
after?: string | null | undefined;
first?: number | null | undefined;
id: string;
order?: ComplianceExternalURLOrder | null | undefined;
};
export type CompliancePageExternalUrlsSection_trustCenterRefetchQuery$data = {
readonly node: {
readonly " $fragmentSpreads": FragmentRefs<"CompliancePageExternalUrlsSection_trustCenterFragment">;
};
};
export type CompliancePageExternalUrlsSection_trustCenterRefetchQuery = {
response: CompliancePageExternalUrlsSection_trustCenterRefetchQuery$data;
variables: CompliancePageExternalUrlsSection_trustCenterRefetchQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "after"
},
v1 = {
"defaultValue": 100,
"kind": "LocalArgument",
"name": "first"
},
v2 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "id"
},
v3 = {
"defaultValue": {
"direction": "ASC",
"field": "RANK"
},
"kind": "LocalArgument",
"name": "order"
},
v4 = [
{
"kind": "Variable",
"name": "id",
"variableName": "id"
}
],
v5 = {
"kind": "Variable",
"name": "after",
"variableName": "after"
},
v6 = {
"kind": "Variable",
"name": "first",
"variableName": "first"
},
v7 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
v8 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v9 = [
(v5/*: any*/),
(v6/*: any*/),
{
"kind": "Variable",
"name": "orderBy",
"variableName": "order"
}
];
return {
"fragment": {
"argumentDefinitions": [
(v0/*: any*/),
(v1/*: any*/),
(v2/*: any*/),
(v3/*: any*/)
],
"kind": "Fragment",
"metadata": null,
"name": "CompliancePageExternalUrlsSection_trustCenterRefetchQuery",
"selections": [
{
"alias": null,
"args": (v4/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"args": [
(v5/*: any*/),
(v6/*: any*/),
{
"kind": "Variable",
"name": "order",
"variableName": "order"
}
],
"kind": "FragmentSpread",
"name": "CompliancePageExternalUrlsSection_trustCenterFragment"
}
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [
(v0/*: any*/),
(v1/*: any*/),
(v3/*: any*/),
(v2/*: any*/)
],
"kind": "Operation",
"name": "CompliancePageExternalUrlsSection_trustCenterRefetchQuery",
"selections": [
{
"alias": null,
"args": (v4/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v7/*: any*/),
(v8/*: any*/),
{
"kind": "InlineFragment",
"selections": [
{
"alias": "canUpdate",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:trust-center:update"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:trust-center:update\")"
},
{
"alias": null,
"args": (v9/*: any*/),
"concreteType": "ComplianceExternalURLConnection",
"kind": "LinkedField",
"name": "externalUrls",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "ComplianceExternalURLEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "ComplianceExternalURL",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v8/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "url",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "rank",
"storageKey": null
},
(v7/*: any*/)
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
}
],
"storageKey": null
},
{
"kind": "ClientExtension",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__id",
"storageKey": null
}
]
}
],
"storageKey": null
},
{
"alias": null,
"args": (v9/*: any*/),
"filters": [
"orderBy"
],
"handle": "connection",
"key": "CompliancePageExternalUrlsSection_externalUrls",
"kind": "LinkedHandle",
"name": "externalUrls"
}
],
"type": "TrustCenter",
"abstractKey": null
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "0732bd06586ed31ed89b4dfad14d1fbd",
"id": null,
"metadata": {},
"name": "CompliancePageExternalUrlsSection_trustCenterRefetchQuery",
"operationKind": "query",
"text": "query CompliancePageExternalUrlsSection_trustCenterRefetchQuery(\n $after: CursorKey = null\n $first: Int = 100\n $order: ComplianceExternalURLOrder = {field: RANK, direction: ASC}\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...CompliancePageExternalUrlsSection_trustCenterFragment_gOVF5\n id\n }\n}\n\nfragment CompliancePageExternalUrlsSection_trustCenterFragment_gOVF5 on TrustCenter {\n id\n canUpdate: permission(action: \"core:trust-center:update\")\n externalUrls(first: $first, after: $after, orderBy: $order) {\n edges {\n node {\n id\n name\n url\n rank\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n"
}
};
})();
(node as any).hash = "f2614c45eaf65a9d6c6f857abc7c66f6";
export default node;

View File

@@ -0,0 +1,132 @@
/**
* @generated SignedSource<<f8aca586d6c3f69c7b0c5d485bdcc101>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type UpdateComplianceExternalURLInput = {
id: string;
name: string;
rank?: number | null | undefined;
url: string;
};
export type CompliancePageExternalUrlsSection_updateMutation$variables = {
input: UpdateComplianceExternalURLInput;
};
export type CompliancePageExternalUrlsSection_updateMutation$data = {
readonly updateComplianceExternalURL: {
readonly complianceExternalUrl: {
readonly id: string;
readonly name: string;
readonly rank: number;
readonly url: string;
};
};
};
export type CompliancePageExternalUrlsSection_updateMutation = {
response: CompliancePageExternalUrlsSection_updateMutation$data;
variables: CompliancePageExternalUrlsSection_updateMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
}
],
v1 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
"concreteType": "UpdateComplianceExternalURLPayload",
"kind": "LinkedField",
"name": "updateComplianceExternalURL",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "ComplianceExternalURL",
"kind": "LinkedField",
"name": "complianceExternalUrl",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "url",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "rank",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "CompliancePageExternalUrlsSection_updateMutation",
"selections": (v1/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "CompliancePageExternalUrlsSection_updateMutation",
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "f771e54a9a15080525576308c8277815",
"id": null,
"metadata": {},
"name": "CompliancePageExternalUrlsSection_updateMutation",
"operationKind": "mutation",
"text": "mutation CompliancePageExternalUrlsSection_updateMutation(\n $input: UpdateComplianceExternalURLInput!\n) {\n updateComplianceExternalURL(input: $input) {\n complianceExternalUrl {\n id\n name\n url\n rank\n }\n }\n}\n"
}
};
})();
(node as any).hash = "b6162bd7b54e784dff62db83e0ff32be";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<cf316aa4cda5fc4c9aebae8f0f10c365>>
* @generated SignedSource<<18bb7ea1b507c374c33d9f4f7a56db2d>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -17,7 +17,6 @@ export type CompliancePageOverviewPageQuery$data = {
readonly organization: {
readonly compliancePage?: {
readonly canGetNDA: boolean;
readonly " $fragmentSpreads": FragmentRefs<"CompliancePageFrameworkList_compliancePageFragment">;
} | null | undefined;
readonly " $fragmentSpreads": FragmentRefs<"CompliancePageNDASectionFragment" | "CompliancePageSlackSectionFragment" | "CompliancePageStatusSectionFragment">;
};
@@ -56,35 +55,12 @@ v2 = {
"storageKey": "permission(action:\"core:trust-center:get-nda\")"
},
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
v4 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v5 = {
"kind": "Literal",
"name": "first",
"value": 100
},
v6 = [
(v5/*: any*/),
{
"kind": "Literal",
"name": "orderBy",
"value": {
"direction": "ASC",
"field": "RANK"
}
}
];
};
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
@@ -111,12 +87,7 @@ return {
"name": "trustCenter",
"plural": false,
"selections": [
(v2/*: any*/),
{
"args": null,
"kind": "FragmentSpread",
"name": "CompliancePageFrameworkList_compliancePageFragment"
}
(v2/*: any*/)
],
"storageKey": null
}
@@ -160,7 +131,13 @@ return {
"name": "node",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
{
"kind": "InlineFragment",
"selections": [
@@ -173,7 +150,14 @@ return {
"plural": false,
"selections": [
(v2/*: any*/),
(v4/*: any*/),
(v3/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "active",
"storageKey": null
},
{
"alias": "canUpdate",
"args": [
@@ -187,150 +171,6 @@ return {
"name": "permission",
"storageKey": "permission(action:\"core:trust-center:update\")"
},
{
"alias": null,
"args": (v6/*: any*/),
"concreteType": "ComplianceFrameworkConnection",
"kind": "LinkedField",
"name": "complianceFrameworks",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "ComplianceFrameworkEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "ComplianceFramework",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v4/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "rank",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "visibility",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "Framework",
"kind": "LinkedField",
"name": "framework",
"plural": false,
"selections": [
(v4/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "lightLogoURL",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "darkLogoURL",
"storageKey": null
}
],
"storageKey": null
},
(v3/*: any*/)
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
}
],
"storageKey": null
},
{
"kind": "ClientExtension",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__id",
"storageKey": null
}
]
}
],
"storageKey": "complianceFrameworks(first:100,orderBy:{\"direction\":\"ASC\",\"field\":\"RANK\"})"
},
{
"alias": null,
"args": (v6/*: any*/),
"filters": [
"orderBy"
],
"handle": "connection",
"key": "CompliancePageFrameworkList_complianceFrameworks",
"kind": "LinkedHandle",
"name": "complianceFrameworks"
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "active",
"storageKey": null
},
{
"alias": null,
"args": null,
@@ -389,14 +229,18 @@ return {
"name": "domain",
"storageKey": null
},
(v4/*: any*/)
(v3/*: any*/)
],
"storageKey": null
},
{
"alias": null,
"args": [
(v5/*: any*/)
{
"kind": "Literal",
"name": "first",
"value": 100
}
],
"concreteType": "SlackConnectionConnection",
"kind": "LinkedField",
@@ -419,7 +263,7 @@ return {
"name": "node",
"plural": false,
"selections": [
(v4/*: any*/),
(v3/*: any*/),
{
"alias": null,
"args": null,
@@ -447,23 +291,23 @@ return {
"type": "Organization",
"abstractKey": null
},
(v4/*: any*/)
(v3/*: any*/)
],
"storageKey": null
}
]
},
"params": {
"cacheID": "e6cc68fe4df4a8dffc558f6db82ead9c",
"cacheID": "0041132d3ac46a3193d9acbd6f27bdf5",
"id": null,
"metadata": {},
"name": "CompliancePageOverviewPageQuery",
"operationKind": "query",
"text": "query CompliancePageOverviewPageQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n compliancePage: trustCenter {\n canGetNDA: permission(action: \"core:trust-center:get-nda\")\n ...CompliancePageFrameworkList_compliancePageFragment\n id\n }\n }\n ...CompliancePageStatusSectionFragment\n ...CompliancePageNDASectionFragment\n ...CompliancePageSlackSectionFragment\n id\n }\n}\n\nfragment CompliancePageFrameworkList_compliancePageFragment on TrustCenter {\n id\n canUpdate: permission(action: \"core:trust-center:update\")\n complianceFrameworks(first: 100, orderBy: {field: RANK, direction: ASC}) {\n edges {\n node {\n id\n rank\n visibility\n framework {\n id\n name\n lightLogoURL\n darkLogoURL\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n\nfragment CompliancePageNDASectionFragment on Organization {\n compliancePage: trustCenter {\n id\n ndaFileName\n ndaFileUrl\n canUpdate: permission(action: \"core:trust-center:update\")\n canUploadNDA: permission(action: \"core:trust-center:upload-nda\")\n canDeleteNDA: permission(action: \"core:trust-center:delete-nda\")\n }\n}\n\nfragment CompliancePageSlackSectionFragment on Organization {\n compliancePage: trustCenter {\n canUpdate: permission(action: \"core:trust-center:update\")\n id\n }\n slackConnections(first: 100) {\n edges {\n node {\n id\n channel\n createdAt\n }\n }\n }\n}\n\nfragment CompliancePageStatusSectionFragment on Organization {\n customDomain {\n domain\n id\n }\n compliancePage: trustCenter {\n id\n active\n canUpdate: permission(action: \"core:trust-center:update\")\n }\n}\n"
"text": "query CompliancePageOverviewPageQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n compliancePage: trustCenter {\n canGetNDA: permission(action: \"core:trust-center:get-nda\")\n id\n }\n }\n ...CompliancePageStatusSectionFragment\n ...CompliancePageNDASectionFragment\n ...CompliancePageSlackSectionFragment\n id\n }\n}\n\nfragment CompliancePageNDASectionFragment on Organization {\n compliancePage: trustCenter {\n id\n ndaFileName\n ndaFileUrl\n canUpdate: permission(action: \"core:trust-center:update\")\n canUploadNDA: permission(action: \"core:trust-center:upload-nda\")\n canDeleteNDA: permission(action: \"core:trust-center:delete-nda\")\n }\n}\n\nfragment CompliancePageSlackSectionFragment on Organization {\n compliancePage: trustCenter {\n canUpdate: permission(action: \"core:trust-center:update\")\n id\n }\n slackConnections(first: 100) {\n edges {\n node {\n id\n channel\n createdAt\n }\n }\n }\n}\n\nfragment CompliancePageStatusSectionFragment on Organization {\n customDomain {\n domain\n id\n }\n compliancePage: trustCenter {\n id\n active\n canUpdate: permission(action: \"core:trust-center:update\")\n }\n}\n"
}
};
})();
(node as any).hash = "be0dff83941c21755ea2ff77a865805b";
(node as any).hash = "689133b7f1c380266e6f15a60c527c91";
export default node;

View File

@@ -17,6 +17,9 @@ import type { CompliancePageBrandPage_updateMutation } from "#/__generated__/cor
import type { CompliancePageBrandPageQuery } from "#/__generated__/core/CompliancePageBrandPageQuery.graphql";
import { useMutationWithToasts } from "#/hooks/useMutationWithToasts";
import { CompliancePageExternalUrlsSection } from "../overview/_components/CompliancePageExternalUrlsSection";
import { CompliancePageFrameworkList } from "../overview/_components/CompliancePageFrameworkList";
export const compliancePageBrandPageQuery = graphql`
query CompliancePageBrandPageQuery($organizationId: ID!) {
organization: node(id: $organizationId) {
@@ -27,6 +30,8 @@ export const compliancePageBrandPageQuery = graphql`
logoFileUrl
darkLogoFileUrl
canUpdate: permission(action: "core:trust-center:update")
...CompliancePageFrameworkList_compliancePageFragment
...CompliancePageExternalUrlsSection_trustCenterFragment
}
}
}
@@ -218,116 +223,130 @@ export function CompliancePageBrandPage(props: { queryRef: PreloadedQuery<Compli
const currentDarkLogoUrl = darkLogoPreview || darkLogoFileUrl;
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<h2 className="text-base font-medium">{__("Branding")}</h2>
{isUpdating && <Spinner />}
<div className="space-y-8">
<div className="space-y-4">
<div className="flex items-center justify-between">
<h2 className="text-base font-medium">{__("Branding")}</h2>
{isUpdating && <Spinner />}
</div>
<Card padded className="space-y-4">
<div className="flex gap-6 items-start">
<div className="flex-1">
<Label>{__("Logo")}</Label>
<p className="text-sm text-txt-tertiary mb-3">
{__("This logo will be displayed on your public compliance page.")}
</p>
{currentLogoUrl
? (
<div className="flex items-center gap-4">
<div className="border border-border-solid rounded-md p-4 bg-surface-secondary">
<img
src={currentLogoUrl}
alt={__("Compliance page logo")}
className="h-16 max-w-xs object-contain"
/>
</div>
<FileButton
disabled={disabled}
onChange={handleLogoChange}
variant="secondary"
accept="image/png,image/jpeg,image/jpg,image/svg+xml,image/webp"
>
{isUpdating ? __("Uploading...") : __("Change logo")}
</FileButton>
<Button
type="button"
variant="quaternary"
icon={IconTrashCan}
onClick={() => void handleRemoveLogo()}
disabled={disabled}
aria-label={__("Remove logo")}
className="text-red-600 hover:text-red-700"
/>
</div>
)
: (
<Dropzone
description={__("Upload logo image (PNG, JPG, SVG, WEBP up to 5MB)")}
isUploading={isUpdating}
onDrop={handleLogoDrop}
accept={{
"image/png": [".png"],
"image/jpeg": [".jpg", ".jpeg"],
"image/svg+xml": [".svg"],
"image/webp": [".webp"],
}}
maxSize={5}
/>
)}
</div>
<div className="flex-1">
<Label>{__("Dark mode logo")}</Label>
<p className="text-sm text-txt-tertiary mb-3">
{__("This logo will be used when dark mode is enabled.")}
</p>
{currentDarkLogoUrl
? (
<div className="flex items-center gap-4">
<div className="border border-border-solid rounded-md p-4 bg-gray-900">
<img
src={currentDarkLogoUrl}
alt={__("Compliance page dark logo")}
className="h-16 max-w-xs object-contain"
/>
</div>
<FileButton
disabled={disabled}
onChange={handleDarkLogoChange}
variant="secondary"
accept="image/png,image/jpeg,image/jpg,image/svg+xml,image/webp"
>
{isUpdating ? __("Uploading...") : __("Change dark logo")}
</FileButton>
<Button
type="button"
variant="quaternary"
icon={IconTrashCan}
onClick={() => void handleRemoveDarkLogo()}
disabled={disabled}
aria-label={__("Remove dark logo")}
className="text-red-600 hover:text-red-700"
/>
</div>
)
: (
<Dropzone
description={__("Upload dark logo image (PNG, JPG, SVG, WEBP up to 5MB)")}
isUploading={isUpdating}
onDrop={handleDarkLogoDrop}
accept={{
"image/png": [".png"],
"image/jpeg": [".jpg", ".jpeg"],
"image/svg+xml": [".svg"],
"image/webp": [".webp"],
}}
maxSize={5}
/>
)}
</div>
</div>
</Card>
</div>
<Card padded className="space-y-4">
<div className="flex gap-6 items-start">
<div className="flex-1">
<Label>{__("Logo")}</Label>
<p className="text-sm text-txt-tertiary mb-3">
{__("This logo will be displayed on your public compliance page.")}
</p>
{currentLogoUrl
? (
<div className="flex items-center gap-4">
<div className="border border-border-solid rounded-md p-4 bg-surface-secondary">
<img
src={currentLogoUrl}
alt={__("Compliance page logo")}
className="h-16 max-w-xs object-contain"
/>
</div>
<FileButton
disabled={disabled}
onChange={handleLogoChange}
variant="secondary"
accept="image/png,image/jpeg,image/jpg,image/svg+xml,image/webp"
>
{isUpdating ? __("Uploading...") : __("Change logo")}
</FileButton>
<Button
type="button"
variant="quaternary"
icon={IconTrashCan}
onClick={() => void handleRemoveLogo()}
disabled={disabled}
aria-label={__("Remove logo")}
className="text-red-600 hover:text-red-700"
/>
</div>
)
: (
<Dropzone
description={__("Upload logo image (PNG, JPG, SVG, WEBP up to 5MB)")}
isUploading={isUpdating}
onDrop={handleLogoDrop}
accept={{
"image/png": [".png"],
"image/jpeg": [".jpg", ".jpeg"],
"image/svg+xml": [".svg"],
"image/webp": [".webp"],
}}
maxSize={5}
/>
)}
</div>
<div className="flex-1">
<Label>{__("Dark mode logo")}</Label>
<p className="text-sm text-txt-tertiary mb-3">
{__("This logo will be used when dark mode is enabled.")}
</p>
{currentDarkLogoUrl
? (
<div className="flex items-center gap-4">
<div className="border border-border-solid rounded-md p-4 bg-gray-900">
<img
src={currentDarkLogoUrl}
alt={__("Compliance page dark logo")}
className="h-16 max-w-xs object-contain"
/>
</div>
<FileButton
disabled={disabled}
onChange={handleDarkLogoChange}
variant="secondary"
accept="image/png,image/jpeg,image/jpg,image/svg+xml,image/webp"
>
{isUpdating ? __("Uploading...") : __("Change dark logo")}
</FileButton>
<Button
type="button"
variant="quaternary"
icon={IconTrashCan}
onClick={() => void handleRemoveDarkLogo()}
disabled={disabled}
aria-label={__("Remove dark logo")}
className="text-red-600 hover:text-red-700"
/>
</div>
)
: (
<Dropzone
description={__("Upload dark logo image (PNG, JPG, SVG, WEBP up to 5MB)")}
isUploading={isUpdating}
onDrop={handleDarkLogoDrop}
accept={{
"image/png": [".png"],
"image/jpeg": [".jpg", ".jpeg"],
"image/svg+xml": [".svg"],
"image/webp": [".webp"],
}}
maxSize={5}
/>
)}
</div>
<div className="space-y-4">
<div>
<h3 className="text-base font-medium">{__("Frameworks")}</h3>
<p className="text-sm text-txt-tertiary">
{__("Select which frameworks to display as badges on your compliance page")}
</p>
</div>
</Card>
<CompliancePageFrameworkList compliancePageRef={organization.compliancePage} />
</div>
<CompliancePageExternalUrlsSection trustCenterRef={organization.compliancePage} />
</div>
);
}

View File

@@ -1,10 +1,8 @@
import { useTranslate } from "@probo/i18n";
import { type PreloadedQuery, usePreloadedQuery } from "react-relay";
import { graphql } from "relay-runtime";
import type { CompliancePageOverviewPageQuery } from "#/__generated__/core/CompliancePageOverviewPageQuery.graphql";
import { CompliancePageFrameworkList } from "./_components/CompliancePageFrameworkList";
import { CompliancePageNDASection } from "./_components/CompliancePageNDASection";
import { CompliancePageSlackSection } from "./_components/CompliancePageSlackSection";
import { CompliancePageStatusSection } from "./_components/CompliancePageStatusSection";
@@ -15,7 +13,6 @@ export const compliancePageOverviewPageQuery = graphql`
... on Organization {
compliancePage: trustCenter {
canGetNDA: permission(action: "core:trust-center:get-nda")
...CompliancePageFrameworkList_compliancePageFragment
}
}
...CompliancePageStatusSectionFragment
@@ -28,8 +25,6 @@ export const compliancePageOverviewPageQuery = graphql`
export function CompliancePageOverviewPage(props: { queryRef: PreloadedQuery<CompliancePageOverviewPageQuery> }) {
const { queryRef } = props;
const { __ } = useTranslate();
const { organization } = usePreloadedQuery<CompliancePageOverviewPageQuery>(
compliancePageOverviewPageQuery,
queryRef,
@@ -44,18 +39,6 @@ export function CompliancePageOverviewPage(props: { queryRef: PreloadedQuery<Com
)}
<CompliancePageSlackSection fragmentRef={organization} />
{organization.compliancePage && (
<div className="space-y-4">
<div>
<h3 className="text-base font-medium">{__("Frameworks")}</h3>
<p className="text-sm text-txt-tertiary">
{__("Select which frameworks to display as badges on your compliance page")}
</p>
</div>
<CompliancePageFrameworkList compliancePageRef={organization.compliancePage} />
</div>
)}
</div>
);
}

View File

@@ -0,0 +1,453 @@
import { detectSocialName, safeOpenUrl } from "@probo/helpers";
import { useTranslate } from "@probo/i18n";
import {
Button,
Dialog,
DialogContent,
DialogFooter,
Field,
IconArrowLink,
IconPencil,
IconPlusLarge,
IconTrashCan,
SocialIcon,
Spinner,
Table,
Tbody,
Td,
Th,
Thead,
Tr,
useDialogRef,
} from "@probo/ui";
import { forwardRef, useCallback, useImperativeHandle, useRef, useState, useTransition } from "react";
import { useRefetchableFragment } from "react-relay";
import { ConnectionHandler, graphql } from "relay-runtime";
import { z } from "zod";
import type { CompliancePageExternalUrlsSection_createMutation } from "#/__generated__/core/CompliancePageExternalUrlsSection_createMutation.graphql";
import type { CompliancePageExternalUrlsSection_deleteMutation } from "#/__generated__/core/CompliancePageExternalUrlsSection_deleteMutation.graphql";
import type { CompliancePageExternalUrlsSection_trustCenterFragment$key } from "#/__generated__/core/CompliancePageExternalUrlsSection_trustCenterFragment.graphql";
import type { CompliancePageExternalUrlsSection_trustCenterRefetchQuery } from "#/__generated__/core/CompliancePageExternalUrlsSection_trustCenterRefetchQuery.graphql";
import type { CompliancePageExternalUrlsSection_updateMutation } from "#/__generated__/core/CompliancePageExternalUrlsSection_updateMutation.graphql";
import { useFormWithSchema } from "#/hooks/useFormWithSchema";
import { useMutationWithToasts } from "#/hooks/useMutationWithToasts";
const trustCenterFragment = graphql`
fragment CompliancePageExternalUrlsSection_trustCenterFragment on TrustCenter
@refetchable(queryName: "CompliancePageExternalUrlsSection_trustCenterRefetchQuery")
@argumentDefinitions(
first: { type: Int, defaultValue: 100 }
after: { type: CursorKey, defaultValue: null }
order: { type: ComplianceExternalURLOrder, defaultValue: { field: RANK, direction: ASC } }
) {
id
canUpdate: permission(action: "core:trust-center:update")
externalUrls(first: $first, after: $after, orderBy: $order)
@connection(key: "CompliancePageExternalUrlsSection_externalUrls", filters: ["orderBy"]) {
__id
edges {
node {
id
name
url
rank
}
}
}
}
`;
const createMutation = graphql`
mutation CompliancePageExternalUrlsSection_createMutation($input: CreateComplianceExternalURLInput!) {
createComplianceExternalURL(input: $input) {
complianceExternalUrlEdge {
node {
id
name
url
rank
}
}
}
}
`;
const updateMutation = graphql`
mutation CompliancePageExternalUrlsSection_updateMutation($input: UpdateComplianceExternalURLInput!) {
updateComplianceExternalURL(input: $input) {
complianceExternalUrl {
id
name
url
rank
}
}
}
`;
const deleteMutation = graphql`
mutation CompliancePageExternalUrlsSection_deleteMutation($input: DeleteComplianceExternalURLInput!) {
deleteComplianceExternalURL(input: $input) {
deletedComplianceExternalUrlId
}
}
`;
const urlSchema = z.object({
name: z.string().min(1, "Name is required"),
url: z.string().url("Please enter a valid URL"),
});
type UrlFormData = z.infer<typeof urlSchema>;
type UrlNode = { id: string; name: string; url: string; rank: number };
type ExternalUrlDialogRef = {
openCreate: (trustCenterId: string, connectionId: string) => void;
openEdit: (node: UrlNode) => void;
};
const ExternalUrlDialog = forwardRef<ExternalUrlDialogRef>(
function ExternalUrlDialog(_, ref) {
const { __ } = useTranslate();
const dialogRef = useDialogRef();
const [mode, setMode] = useState<"create" | "edit">("create");
const [trustCenterId, setTrustCenterId] = useState("");
const [connectionId, setConnectionId] = useState("");
const [editNode, setEditNode] = useState<UrlNode | null>(null);
const [create, isCreating] = useMutationWithToasts<CompliancePageExternalUrlsSection_createMutation>(
createMutation,
{ successMessage: __("Link added successfully."), errorMessage: __("Failed to add link.") },
);
const [update, isUpdating] = useMutationWithToasts<CompliancePageExternalUrlsSection_updateMutation>(
updateMutation,
{ successMessage: __("Link updated successfully."), errorMessage: __("Failed to update link.") },
);
const { register, handleSubmit, formState: { errors }, reset, setValue, watch } = useFormWithSchema(urlSchema, {
defaultValues: { name: "", url: "" },
});
const [nameAutoDetected, setNameAutoDetected] = useState(false);
const handleUrlChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const url = e.target.value;
const detected = detectSocialName(url);
if (detected && (nameAutoDetected || watch("name") === "")) {
setValue("name", detected, { shouldValidate: true });
setNameAutoDetected(true);
} else if (!detected && nameAutoDetected) {
setValue("name", "", { shouldValidate: false });
setNameAutoDetected(false);
}
};
useImperativeHandle(ref, () => ({
openCreate: (tId, cId) => {
setMode("create");
setTrustCenterId(tId);
setConnectionId(cId);
setEditNode(null);
setNameAutoDetected(false);
reset({ name: "", url: "" });
dialogRef.current?.open();
},
openEdit: (node) => {
setMode("edit");
setEditNode(node);
setNameAutoDetected(false);
reset({ name: node.name, url: node.url });
dialogRef.current?.open();
},
}));
const onSubmit = async (data: UrlFormData) => {
if (mode === "create") {
await create({
variables: {
input: { trustCenterId, name: data.name, url: data.url },
},
updater: (store) => {
const payload = store.getRootField("createComplianceExternalURL");
const edge = payload?.getLinkedRecord("complianceExternalUrlEdge");
if (!edge) return;
const connection = store.get(connectionId);
if (!connection) return;
ConnectionHandler.insertEdgeAfter(connection, edge);
},
onCompleted: (_, errs) => {
if (!errs?.length) {
reset();
dialogRef.current?.close();
}
},
});
} else if (editNode) {
await update({
variables: { input: { id: editNode.id, name: data.name, url: data.url } },
onCompleted: (_, errs) => {
if (!errs?.length) {
reset();
dialogRef.current?.close();
}
},
});
}
};
const isSubmitting = isCreating || isUpdating;
const title = mode === "create" ? __("Add link") : __("Edit link");
return (
<Dialog ref={dialogRef} title={title} onClose={() => reset()}>
<form onSubmit={e => void handleSubmit(onSubmit)(e)}>
<DialogContent padded className="space-y-6">
<Field
{...register("url", { onChange: handleUrlChange })}
label={__("URL")}
type="url"
required
placeholder="https://example.com"
error={errors.url?.message}
/>
<Field
{...register("name")}
label={__("Name")}
type="text"
required
placeholder={__("e.g. Twitter, LinkedIn")}
error={errors.name?.message}
/>
</DialogContent>
<DialogFooter>
<Button type="submit" disabled={isSubmitting} icon={isSubmitting ? Spinner : undefined}>
{mode === "create" ? __("Add link") : __("Save changes")}
</Button>
</DialogFooter>
</form>
</Dialog>
);
},
);
function ExternalUrlRow(props: {
node: UrlNode;
canEdit: boolean;
connectionId: string;
isDragging: boolean;
isDropTarget: boolean;
onDragStart: () => void;
onDragOver: (e: React.DragEvent) => void;
onDrop: () => void;
onDragEnd: () => void;
onEdit: (node: UrlNode) => void;
}) {
const { node, canEdit, connectionId, isDragging, isDropTarget, onDragStart, onDragOver, onDrop, onDragEnd, onEdit } = props;
const { __ } = useTranslate();
const [isMouseDown, setIsMouseDown] = useState(false);
const [deleteUrl] = useMutationWithToasts<CompliancePageExternalUrlsSection_deleteMutation>(
deleteMutation,
{ successMessage: __("Link removed."), errorMessage: __("Failed to remove link.") },
);
const handleDelete = () => {
void deleteUrl({
variables: { input: { id: node.id } },
updater: (store) => {
const connection = store.get(connectionId);
if (!connection) return;
ConnectionHandler.deleteNode(connection, node.id);
},
});
};
const className = [
isDragging && "opacity-50 cursor-grabbing",
!isDragging && !isMouseDown && "cursor-grab",
!isDragging && isMouseDown && "cursor-grabbing",
isDropTarget && "!bg-primary-50 border-y-2 border-primary-500",
]
.filter(Boolean)
.join(" ");
return (
<Tr
draggable={canEdit}
onDragStart={canEdit ? onDragStart : undefined}
onDragOver={canEdit ? onDragOver : undefined}
onDrop={canEdit ? onDrop : undefined}
onDragEnd={canEdit ? onDragEnd : undefined}
onMouseDown={canEdit ? () => setIsMouseDown(true) : undefined}
onMouseUp={canEdit ? () => setIsMouseDown(false) : undefined}
onMouseLeave={canEdit ? () => setIsMouseDown(false) : undefined}
className={className}
>
<Td>
<div className="flex items-center gap-3 text-txt-secondary">
<SocialIcon socialName={detectSocialName(node.url)} size={16} className="shrink-0" />
<span className="font-medium">{node.name}</span>
</div>
</Td>
<Td>
<span className="text-txt-secondary truncate">{node.url}</span>
</Td>
<Td noLink width={canEdit ? 144 : 56} className="text-end">
<div className="flex gap-2 justify-end">
<Button variant="secondary" icon={IconArrowLink} onClick={() => safeOpenUrl(node.url)} />
{canEdit && (
<>
<Button variant="secondary" icon={IconPencil} onClick={() => onEdit(node)} />
<Button variant="danger" icon={IconTrashCan} onClick={handleDelete} />
</>
)}
</div>
</Td>
</Tr>
);
}
export function CompliancePageExternalUrlsSection(props: {
trustCenterRef: CompliancePageExternalUrlsSection_trustCenterFragment$key;
}) {
const { __ } = useTranslate();
const [, startTransition] = useTransition();
const dialogRef = useRef<ExternalUrlDialogRef>(null);
const [trustCenter, refetch] = useRefetchableFragment<
CompliancePageExternalUrlsSection_trustCenterRefetchQuery,
CompliancePageExternalUrlsSection_trustCenterFragment$key
>(trustCenterFragment, props.trustCenterRef);
const [draggedIndex, setDraggedIndex] = useState<number | null>(null);
const [dragOverIndex, setDragOverIndex] = useState<number | null>(null);
const [updateRank] = useMutationWithToasts<CompliancePageExternalUrlsSection_updateMutation>(
updateMutation,
{ successMessage: __("Order updated."), errorMessage: __("Failed to update order.") },
);
const edges = trustCenter.externalUrls.edges;
const canEdit = trustCenter.canUpdate;
const connectionId = trustCenter.externalUrls.__id;
const handleCreate = () => {
dialogRef.current?.openCreate(trustCenter.id, connectionId);
};
const handleEdit = (node: UrlNode) => {
dialogRef.current?.openEdit(node);
};
const handleDragOver = (e: React.DragEvent, index: number) => {
e.preventDefault();
if (draggedIndex !== index) setDragOverIndex(index);
};
const handleDrop = useCallback(
async (targetIndex: number) => {
if (draggedIndex === null || draggedIndex === targetIndex) {
setDraggedIndex(null);
setDragOverIndex(null);
return;
}
const draggedEdge = edges[draggedIndex];
const targetRank = edges[targetIndex].node.rank;
const draggedId = draggedEdge.node.id;
await updateRank({
variables: { input: { id: draggedId, name: draggedEdge.node.name, url: draggedEdge.node.url, rank: targetRank } },
updater: (store) => {
const connection = store.get(connectionId);
if (!connection) return;
const storeEdges = connection.getLinkedRecords("edges");
if (!storeEdges) return;
const fromIdx = storeEdges.findIndex(e => e.getLinkedRecord("node")?.getDataID() === draggedId);
const toIdx = storeEdges.findIndex(e => e.getLinkedRecord("node")?.getDataID() === edges[targetIndex].node.id);
if (fromIdx === -1 || toIdx === -1) return;
const reordered = [...storeEdges];
const [moved] = reordered.splice(fromIdx, 1);
reordered.splice(toIdx, 0, moved);
connection.setLinkedRecords(reordered, "edges");
},
onCompleted: (_, errors) => {
startTransition(() => {
refetch({}, { fetchPolicy: errors?.length ? "network-only" : "store-and-network" });
});
},
});
setDraggedIndex(null);
setDragOverIndex(null);
},
[draggedIndex, edges, connectionId, updateRank, refetch, startTransition],
);
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<div>
<h3 className="text-base font-medium">{__("Custom links")}</h3>
<p className="text-sm text-txt-tertiary">
{__("Add external URLs to display on your compliance page")}
</p>
</div>
{canEdit && (
<Button icon={IconPlusLarge} onClick={handleCreate}>
{__("Add link")}
</Button>
)}
</div>
<Table>
<Thead>
<Tr>
<Th>{__("Name")}</Th>
<Th>{__("URL")}</Th>
<Th />
</Tr>
</Thead>
<Tbody>
{edges.length === 0 && (
<Tr>
<Td colSpan={3} className="text-center text-txt-secondary">
{__("No custom links yet")}
</Td>
</Tr>
)}
{edges.map(({ node }, index) => (
<ExternalUrlRow
key={node.id}
node={node}
canEdit={canEdit}
connectionId={connectionId}
isDragging={draggedIndex === index}
isDropTarget={dragOverIndex === index && draggedIndex !== index}
onDragStart={() => setDraggedIndex(index)}
onDragOver={e => handleDragOver(e, index)}
onDrop={() => void handleDrop(index)}
onDragEnd={() => {
setDraggedIndex(null);
setDragOverIndex(null);
}}
onEdit={handleEdit}
/>
))}
</Tbody>
</Table>
{edges.length > 1 && canEdit && (
<p className="text-sm text-txt-tertiary">
{__("Drag and drop to change the displayed order")}
</p>
)}
<ExternalUrlDialog ref={dialogRef} />
</div>
);
}

View File

@@ -1,4 +1,4 @@
import { formatError } from "@probo/helpers";
import { detectSocialName, externalLinkProps, formatError } from "@probo/helpers";
import { useSystemTheme } from "@probo/hooks";
import { useTranslate } from "@probo/i18n";
import { UnAuthenticatedError } from "@probo/relay";
@@ -8,6 +8,7 @@ import {
IconBlock,
IconLock,
IconMedal,
SocialIcon,
useToast,
} from "@probo/ui";
import { type PropsWithChildren } from "react";
@@ -125,11 +126,7 @@ export function OrganizationSidebar({
</h2>
{trustCenter.organization.websiteUrl && (
<BusinessInfo label={__("Website")}>
<a
href={trustCenter.organization.websiteUrl}
target="_blank"
rel="noopener noreferrer"
>
<a {...externalLinkProps(trustCenter.organization.websiteUrl)}>
<span className="text-txt-info hover:underline ">
{new URL(trustCenter.organization.websiteUrl).host}
</span>
@@ -150,6 +147,20 @@ export function OrganizationSidebar({
{trustCenter.organization.headquarterAddress}
</BusinessInfo>
)}
{trustCenter.externalUrls.edges.length > 0 && (
<div className="flex flex-wrap gap-x-4 gap-y-2">
{trustCenter.externalUrls.edges.map(({ node }) => (
<a
key={node.id}
{...externalLinkProps(node.url)}
className="flex items-center gap-1.5 text-sm text-txt-secondary hover:text-txt-primary transition-colors"
>
<SocialIcon socialName={detectSocialName(node.url)} size={14} className="shrink-0" />
<span>{node.name}</span>
</a>
))}
</div>
)}
<hr className="my-6 -mx-6 h-px bg-border-low border-none" />

View File

@@ -22,6 +22,15 @@ export const currentTrustGraphQuery = graphql`
email
headquarterAddress
}
externalUrls(first: 20) {
edges {
node {
id
name
url
}
}
}
...OverviewPageFragment
vendorInfo: vendors(first: 0) {
totalCount

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<4eef287e3e3152affa1b16189a504c36>>
* @generated SignedSource<<74757bf9a32552f6353eb888c9f663cd>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -33,6 +33,15 @@ export type TrustGraphCurrentQuery$data = {
}>;
};
readonly darkLogoFileUrl: string | null | undefined;
readonly externalUrls: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly id: string;
readonly name: string;
readonly url: string;
};
}>;
};
readonly id: string;
readonly logoFileUrl: string | null | undefined;
readonly nonDisclosureAgreement: {
@@ -147,6 +156,54 @@ v11 = {
"storageKey": null
},
v12 = {
"alias": null,
"args": [
{
"kind": "Literal",
"name": "first",
"value": 20
}
],
"concreteType": "ComplianceExternalURLConnection",
"kind": "LinkedField",
"name": "externalUrls",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "ComplianceExternalURLEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "ComplianceExternalURL",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v0/*: any*/),
(v7/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "url",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "externalUrls(first:20)"
},
v13 = {
"alias": "vendorInfo",
"args": [
{
@@ -170,28 +227,28 @@ v12 = {
],
"storageKey": "vendors(first:0)"
},
v13 = [
v14 = [
{
"kind": "Literal",
"name": "first",
"value": 50
}
],
v14 = [
v15 = [
{
"kind": "Literal",
"name": "first",
"value": 5
}
],
v15 = {
v16 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "isUserAuthorized",
"storageKey": null
},
v16 = {
v17 = {
"alias": null,
"args": null,
"concreteType": "DocumentAccess",
@@ -204,7 +261,7 @@ v16 = {
],
"storageKey": null
},
v17 = {
v18 = {
"alias": null,
"args": null,
"concreteType": "Framework",
@@ -293,15 +350,16 @@ return {
],
"storageKey": null
},
(v12/*: any*/),
{
"args": null,
"kind": "FragmentSpread",
"name": "OverviewPageFragment"
},
(v12/*: any*/),
(v13/*: any*/),
{
"alias": null,
"args": (v13/*: any*/),
"args": (v14/*: any*/),
"concreteType": "AuditConnection",
"kind": "LinkedField",
"name": "audits",
@@ -340,7 +398,7 @@ return {
},
{
"alias": null,
"args": (v13/*: any*/),
"args": (v14/*: any*/),
"concreteType": "ComplianceFrameworkConnection",
"kind": "LinkedField",
"name": "complianceFrameworks",
@@ -458,6 +516,7 @@ return {
],
"storageKey": null
},
(v12/*: any*/),
{
"alias": null,
"args": [
@@ -559,7 +618,7 @@ return {
},
{
"alias": null,
"args": (v14/*: any*/),
"args": (v15/*: any*/),
"concreteType": "DocumentConnection",
"kind": "LinkedField",
"name": "documents",
@@ -589,8 +648,8 @@ return {
"name": "title",
"storageKey": null
},
(v15/*: any*/),
(v16/*: any*/),
(v17/*: any*/),
{
"alias": null,
"args": null,
@@ -609,7 +668,7 @@ return {
},
{
"alias": null,
"args": (v14/*: any*/),
"args": (v15/*: any*/),
"concreteType": "TrustCenterFileConnection",
"kind": "LinkedField",
"name": "trustCenterFiles",
@@ -640,8 +699,8 @@ return {
"storageKey": null
},
(v7/*: any*/),
(v15/*: any*/),
(v16/*: any*/)
(v16/*: any*/),
(v17/*: any*/)
],
"storageKey": null
}
@@ -651,10 +710,10 @@ return {
],
"storageKey": "trustCenterFiles(first:5)"
},
(v12/*: any*/),
(v13/*: any*/),
{
"alias": null,
"args": (v13/*: any*/),
"args": (v14/*: any*/),
"concreteType": "AuditConnection",
"kind": "LinkedField",
"name": "audits",
@@ -694,12 +753,12 @@ return {
"name": "filename",
"storageKey": null
},
(v15/*: any*/),
(v16/*: any*/)
(v16/*: any*/),
(v17/*: any*/)
],
"storageKey": null
},
(v17/*: any*/)
(v18/*: any*/)
],
"storageKey": null
}
@@ -711,7 +770,7 @@ return {
},
{
"alias": null,
"args": (v13/*: any*/),
"args": (v14/*: any*/),
"concreteType": "ComplianceFrameworkConnection",
"kind": "LinkedField",
"name": "complianceFrameworks",
@@ -734,7 +793,7 @@ return {
"plural": false,
"selections": [
(v0/*: any*/),
(v17/*: any*/)
(v18/*: any*/)
],
"storageKey": null
}
@@ -750,16 +809,16 @@ return {
]
},
"params": {
"cacheID": "e64b984bdcf5a5fb2f19e69c6205ba9a",
"cacheID": "96e75937f385e17b760440e001cefc47",
"id": null,
"metadata": {},
"name": "TrustGraphCurrentQuery",
"operationKind": "query",
"text": "query TrustGraphCurrentQuery {\n currentTrustCenter {\n id\n slug\n logoFileUrl\n darkLogoFileUrl\n nonDisclosureAgreement {\n fileName\n fileUrl\n viewerSignature {\n status\n id\n }\n }\n organization {\n name\n description\n websiteUrl\n email\n headquarterAddress\n id\n }\n ...OverviewPageFragment\n vendorInfo: vendors(first: 0) {\n totalCount\n }\n audits(first: 50) {\n edges {\n node {\n id\n ...AuditRowFragment\n }\n }\n }\n complianceFrameworks(first: 50) {\n edges {\n node {\n id\n framework {\n ...FrameworkBadgeFragment\n id\n }\n }\n }\n }\n }\n}\n\nfragment AuditRowFragment on Audit {\n name\n report {\n id\n filename\n isUserAuthorized\n access {\n id\n status\n }\n }\n framework {\n id\n name\n lightLogoURL\n darkLogoURL\n }\n}\n\nfragment DocumentRowFragment on Document {\n id\n title\n isUserAuthorized\n access {\n id\n status\n }\n}\n\nfragment FrameworkBadgeFragment on Framework {\n id\n name\n lightLogoURL\n darkLogoURL\n}\n\nfragment OverviewPageFragment on TrustCenter {\n references(first: 14) {\n edges {\n node {\n id\n name\n logoUrl\n websiteUrl\n }\n }\n }\n vendors(first: 3) {\n edges {\n node {\n id\n countries\n ...VendorRowFragment\n }\n }\n }\n documents(first: 5) {\n edges {\n node {\n id\n ...DocumentRowFragment\n documentType\n }\n }\n }\n trustCenterFiles(first: 5) {\n edges {\n node {\n id\n category\n ...TrustCenterFileRowFragment\n }\n }\n }\n}\n\nfragment TrustCenterFileRowFragment on TrustCenterFile {\n id\n name\n isUserAuthorized\n access {\n id\n status\n }\n}\n\nfragment VendorRowFragment on Vendor {\n name\n description\n websiteUrl\n countries\n}\n"
"text": "query TrustGraphCurrentQuery {\n currentTrustCenter {\n id\n slug\n logoFileUrl\n darkLogoFileUrl\n nonDisclosureAgreement {\n fileName\n fileUrl\n viewerSignature {\n status\n id\n }\n }\n organization {\n name\n description\n websiteUrl\n email\n headquarterAddress\n id\n }\n externalUrls(first: 20) {\n edges {\n node {\n id\n name\n url\n }\n }\n }\n ...OverviewPageFragment\n vendorInfo: vendors(first: 0) {\n totalCount\n }\n audits(first: 50) {\n edges {\n node {\n id\n ...AuditRowFragment\n }\n }\n }\n complianceFrameworks(first: 50) {\n edges {\n node {\n id\n framework {\n ...FrameworkBadgeFragment\n id\n }\n }\n }\n }\n }\n}\n\nfragment AuditRowFragment on Audit {\n name\n report {\n id\n filename\n isUserAuthorized\n access {\n id\n status\n }\n }\n framework {\n id\n name\n lightLogoURL\n darkLogoURL\n }\n}\n\nfragment DocumentRowFragment on Document {\n id\n title\n isUserAuthorized\n access {\n id\n status\n }\n}\n\nfragment FrameworkBadgeFragment on Framework {\n id\n name\n lightLogoURL\n darkLogoURL\n}\n\nfragment OverviewPageFragment on TrustCenter {\n references(first: 14) {\n edges {\n node {\n id\n name\n logoUrl\n websiteUrl\n }\n }\n }\n vendors(first: 3) {\n edges {\n node {\n id\n countries\n ...VendorRowFragment\n }\n }\n }\n documents(first: 5) {\n edges {\n node {\n id\n ...DocumentRowFragment\n documentType\n }\n }\n }\n trustCenterFiles(first: 5) {\n edges {\n node {\n id\n category\n ...TrustCenterFileRowFragment\n }\n }\n }\n}\n\nfragment TrustCenterFileRowFragment on TrustCenterFile {\n id\n name\n isUserAuthorized\n access {\n id\n status\n }\n}\n\nfragment VendorRowFragment on Vendor {\n name\n description\n websiteUrl\n countries\n}\n"
}
};
})();
(node as any).hash = "d1ce7e38b42fad48e9f38bf00bb34d56";
(node as any).hash = "ca6f04fecf9bce92407f33e8c0e4bd03";
export default node;

View File

@@ -21,6 +21,25 @@ export function downloadFile(url: string | undefined | null, filename: string) {
document.body.removeChild(link);
}
export function externalLinkProps(url: string): {
href: string | undefined;
target: "_blank";
rel: "noopener noreferrer";
} {
let safeHref: string | undefined;
try {
const parsed = new URL(url);
if (parsed.protocol === "http:" || parsed.protocol === "https:") {
safeHref = url;
} else {
console.error("Invalid URL protocol. Only HTTP and HTTPS URLs are allowed:", url);
}
} catch (error) {
console.error("Invalid URL format:", url, error);
}
return { href: safeHref, target: "_blank", rel: "noopener noreferrer" };
}
export function safeOpenUrl(url: string) {
try {
const parsedUrl = new URL(url);

View File

@@ -13,6 +13,7 @@ export {
export {
withViewTransition,
downloadFile,
externalLinkProps,
safeOpenUrl,
focusSiblingElement,
} from "./dom";
@@ -78,6 +79,7 @@ export {
parseDate,
} from "./date";
export { getTrustCenterUrl } from "./trustCenter";
export { detectSocialName } from "./socialUrl";
export { formatError, type GraphQLError } from "./error";
export { Role, roles, getAssignableRoles } from "./roles";
export {

View File

@@ -0,0 +1,12 @@
const SOCIAL_PATTERNS: Array<[RegExp, string]> = [
[/linkedin\.com/i, "LinkedIn"],
[/(?:twitter|x)\.com/i, "X"],
[/facebook\.com/i, "Facebook"],
];
export function detectSocialName(url: string): string | null {
for (const [pattern, name] of SOCIAL_PATTERNS) {
if (pattern.test(url)) return name;
}
return null;
}

View File

@@ -0,0 +1,14 @@
// Facebook logo — trademark of Meta Platforms, Inc.
// Used here solely for brand identification (nominative fair use).
// SVG path from Simple Icons (https://simpleicons.org), CC0 license.
import type { IconProps } from "./type";
type Props = IconProps & { colored?: boolean };
export function IconBrandFacebook({ size = 24, className, colored = false }: Props) {
return (
<svg width={size} height={size} viewBox="0 0 24 24" fill={colored ? "#1877F2" : "currentColor"} className={className} xmlns="http://www.w3.org/2000/svg">
<path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z" />
</svg>
);
}

View File

@@ -0,0 +1,14 @@
// LinkedIn logo — trademark of LinkedIn Corporation.
// Used here solely for brand identification (nominative fair use).
// SVG path from Simple Icons (https://simpleicons.org), CC0 license.
import type { IconProps } from "./type";
type Props = IconProps & { colored?: boolean };
export function IconBrandLinkedin({ size = 24, className, colored = false }: Props) {
return (
<svg width={size} height={size} viewBox="0 0 24 24" fill={colored ? "#0A66C2" : "currentColor"} className={className} xmlns="http://www.w3.org/2000/svg">
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" />
</svg>
);
}

View File

@@ -0,0 +1,14 @@
// X (formerly Twitter) logo — trademark of X Corp.
// Used here solely for brand identification (nominative fair use).
// SVG path from Simple Icons (https://simpleicons.org), CC0 license.
import type { IconProps } from "./type";
type Props = IconProps & { colored?: boolean };
export function IconBrandX({ size = 24, className, colored = false }: Props) {
return (
<svg width={size} height={size} viewBox="0 0 24 24" fill={colored ? "#000000" : "currentColor"} className={className} xmlns="http://www.w3.org/2000/svg">
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.746l7.73-8.835L1.254 2.25H8.08l4.713 6.097zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
</svg>
);
}

View File

@@ -0,0 +1,12 @@
// Globe icon from Lucide (https://lucide.dev), ISC license.
import type { IconProps } from "./type";
export function IconGlobe({ size = 24, className }: IconProps) {
return (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className} xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12" r="10" />
<path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20" />
<path d="M2 12h20" />
</svg>
);
}

View File

@@ -0,0 +1,16 @@
import { IconBrandFacebook } from "./IconBrandFacebook";
import { IconBrandLinkedin } from "./IconBrandLinkedin";
import { IconBrandX } from "./IconBrandX";
import { IconGlobe } from "./IconGlobe";
import type { IconProps } from "./type";
export type SocialIconProps = IconProps & { colored?: boolean };
export function SocialIcon({ socialName, ...props }: { socialName: string | null } & SocialIconProps) {
switch (socialName) {
case "LinkedIn": return <IconBrandLinkedin {...props} />;
case "X": return <IconBrandX {...props} />;
case "Facebook": return <IconBrandFacebook {...props} />;
default: return <IconGlobe {...props} />;
}
}

View File

@@ -85,3 +85,9 @@ export { IconPageCross } from "./IconPageCross";
export { IconRotateCw } from "./IconRotateCw";
export { IconPin } from "./IconPin";
export { IconMinusLarge } from "./IconMinusLarge";
export { IconBrandFacebook } from "./IconBrandFacebook";
export { IconBrandLinkedin } from "./IconBrandLinkedin";
export { IconBrandX } from "./IconBrandX";
export { IconGlobe } from "./IconGlobe";
export { SocialIcon } from "./SocialIcon";
export type { SocialIconProps } from "./SocialIcon";

View File

@@ -0,0 +1,303 @@
package coredata
import (
"context"
"errors"
"fmt"
"maps"
"time"
"github.com/jackc/pgx/v5"
"go.gearno.de/kit/pg"
"go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/page"
)
type (
ComplianceExternalURL struct {
ID gid.GID `db:"id"`
OrganizationID gid.GID `db:"organization_id"`
TrustCenterID gid.GID `db:"trust_center_id"`
Name string `db:"name"`
URL string `db:"url"`
Rank int `db:"rank"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
}
ComplianceExternalURLs []*ComplianceExternalURL
)
func (c ComplianceExternalURL) CursorKey(orderBy ComplianceExternalURLOrderField) page.CursorKey {
switch orderBy {
case ComplianceExternalURLOrderFieldCreatedAt:
return page.NewCursorKey(c.ID, c.CreatedAt)
case ComplianceExternalURLOrderFieldRank:
return page.NewCursorKey(c.ID, c.Rank)
}
panic(fmt.Sprintf("unsupported order by: %s", orderBy))
}
func (c *ComplianceExternalURL) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM compliance_external_urls WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, c.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query compliance external URL authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (c *ComplianceExternalURL) LoadByID(
ctx context.Context,
conn pg.Conn,
scope Scoper,
id gid.GID,
) error {
q := `
SELECT
id,
organization_id,
trust_center_id,
name,
url,
rank,
created_at,
updated_at
FROM
compliance_external_urls
WHERE
%s
AND id = @id
LIMIT 1;
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{"id": id}
maps.Copy(args, scope.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot query compliance_external_urls: %w", err)
}
result, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[ComplianceExternalURL])
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return ErrResourceNotFound
}
return fmt.Errorf("cannot collect compliance external URL: %w", err)
}
*c = result
return nil
}
func (c *ComplianceExternalURL) Insert(
ctx context.Context,
conn pg.Conn,
scope Scoper,
) error {
q := `
INSERT INTO
compliance_external_urls (
id,
tenant_id,
organization_id,
trust_center_id,
name,
url,
rank,
created_at,
updated_at
)
VALUES (
@id,
@tenant_id,
@organization_id,
@trust_center_id,
@name,
@url,
(SELECT COALESCE(MAX(rank), 0) + 1 FROM compliance_external_urls WHERE trust_center_id = @trust_center_id),
@created_at,
@updated_at
)
RETURNING rank;
`
args := pgx.StrictNamedArgs{
"id": c.ID,
"tenant_id": scope.GetTenantID(),
"organization_id": c.OrganizationID,
"trust_center_id": c.TrustCenterID,
"name": c.Name,
"url": c.URL,
"created_at": c.CreatedAt,
"updated_at": c.UpdatedAt,
}
if err := conn.QueryRow(ctx, q, args).Scan(&c.Rank); err != nil {
return fmt.Errorf("cannot insert compliance external URL: %w", err)
}
return nil
}
func (c *ComplianceExternalURL) Update(
ctx context.Context,
conn pg.Conn,
scope Scoper,
) error {
q := `
UPDATE compliance_external_urls
SET
name = @name,
url = @url,
updated_at = @updated_at
WHERE
%s
AND id = @id;
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{
"id": c.ID,
"name": c.Name,
"url": c.URL,
"updated_at": c.UpdatedAt,
}
maps.Copy(args, scope.SQLArguments())
_, err := conn.Exec(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot update compliance external URL: %w", err)
}
return nil
}
func (c *ComplianceExternalURL) UpdateRank(
ctx context.Context,
conn pg.Conn,
scope Scoper,
) error {
q := `
WITH old AS (
SELECT
rank AS old_rank
FROM compliance_external_urls
WHERE %s AND id = @id AND trust_center_id = @trust_center_id
)
UPDATE compliance_external_urls
SET
rank = CASE
WHEN id = @id THEN @new_rank
ELSE rank + CASE
WHEN @new_rank < old.old_rank THEN 1
WHEN @new_rank > old.old_rank THEN -1
END
END,
updated_at = @updated_at
FROM old
WHERE %s
AND trust_center_id = @trust_center_id
AND (
id = @id
OR (rank BETWEEN LEAST(old.old_rank, @new_rank) AND GREATEST(old.old_rank, @new_rank))
);
`
scopeFragment := scope.SQLFragment()
q = fmt.Sprintf(q, scopeFragment, scopeFragment)
args := pgx.StrictNamedArgs{
"id": c.ID,
"new_rank": c.Rank,
"trust_center_id": c.TrustCenterID,
"updated_at": c.UpdatedAt,
}
maps.Copy(args, scope.SQLArguments())
_, err := conn.Exec(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot update compliance external URL rank: %w", err)
}
return nil
}
func (c *ComplianceExternalURL) Delete(
ctx context.Context,
conn pg.Conn,
scope Scoper,
) error {
q := `
DELETE FROM
compliance_external_urls
WHERE
%s
AND id = @id;
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{"id": c.ID}
maps.Copy(args, scope.SQLArguments())
_, err := conn.Exec(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot delete compliance external URL: %w", err)
}
return nil
}
func (c *ComplianceExternalURLs) LoadByTrustCenterID(
ctx context.Context,
conn pg.Conn,
scope Scoper,
trustCenterID gid.GID,
cursor *page.Cursor[ComplianceExternalURLOrderField],
) error {
q := `
SELECT
id,
organization_id,
trust_center_id,
name,
url,
rank,
created_at,
updated_at
FROM
compliance_external_urls
WHERE
%s
AND trust_center_id = @trust_center_id
AND %s
`
q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment())
args := pgx.NamedArgs{"trust_center_id": trustCenterID}
maps.Copy(args, scope.SQLArguments())
maps.Copy(args, cursor.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot query compliance_external_urls: %w", err)
}
results, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[ComplianceExternalURL])
if err != nil {
return fmt.Errorf("cannot collect compliance external URLs: %w", err)
}
*c = results
return nil
}

View File

@@ -0,0 +1,34 @@
package coredata
type (
ComplianceExternalURLOrderField string
)
const (
ComplianceExternalURLOrderFieldCreatedAt ComplianceExternalURLOrderField = "CREATED_AT"
ComplianceExternalURLOrderFieldRank ComplianceExternalURLOrderField = "RANK"
)
func (p ComplianceExternalURLOrderField) Column() string {
switch p {
case ComplianceExternalURLOrderFieldCreatedAt:
return "created_at"
case ComplianceExternalURLOrderFieldRank:
return "rank"
default:
return string(p)
}
}
func (p ComplianceExternalURLOrderField) String() string {
return string(p)
}
func (p ComplianceExternalURLOrderField) MarshalText() ([]byte, error) {
return []byte(p.String()), nil
}
func (p *ComplianceExternalURLOrderField) UnmarshalText(text []byte) error {
*p = ComplianceExternalURLOrderField(text)
return nil
}

View File

@@ -86,6 +86,7 @@ const (
ElectronicSignatureEventEntityType uint16 = 60
EmailAttachmentEntityType uint16 = 61
ComplianceFrameworkEntityType uint16 = 62
ComplianceExternalURLEntityType uint16 = 63
)
func NewEntityFromID(id gid.GID) (any, bool) {
@@ -212,6 +213,8 @@ func NewEntityFromID(id gid.GID) (any, bool) {
return &EmailAttachment{ID: id}, true
case ComplianceFrameworkEntityType:
return &ComplianceFramework{ID: id}, true
case ComplianceExternalURLEntityType:
return &ComplianceExternalURL{ID: id}, true
default:
return nil, false
}

View File

@@ -0,0 +1,16 @@
CREATE TABLE compliance_external_urls (
id TEXT NOT NULL PRIMARY KEY,
tenant_id TEXT NOT NULL,
organization_id TEXT NOT NULL REFERENCES organizations(id) ON UPDATE CASCADE ON DELETE CASCADE,
trust_center_id TEXT NOT NULL REFERENCES trust_centers(id) ON UPDATE CASCADE ON DELETE CASCADE,
name TEXT NOT NULL,
url TEXT NOT NULL,
rank INTEGER NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL
);
ALTER TABLE compliance_external_urls
ADD CONSTRAINT compliance_external_urls_trust_center_id_rank_key
UNIQUE (trust_center_id, rank)
DEFERRABLE INITIALLY DEFERRED;

View File

@@ -53,6 +53,12 @@ const (
ActionComplianceFrameworkDelete = "core:compliance-framework:delete"
ActionComplianceFrameworkUpdateRank = "core:compliance-framework:update-rank"
// ComplianceExternalURL actions
ActionComplianceExternalURLList = "core:compliance-external-url:list"
ActionComplianceExternalURLCreate = "core:compliance-external-url:create"
ActionComplianceExternalURLUpdate = "core:compliance-external-url:update"
ActionComplianceExternalURLDelete = "core:compliance-external-url:delete"
// TrustCenterFile actions
ActionTrustCenterFileGet = "core:trust-center-file:get"
ActionTrustCenterFileList = "core:trust-center-file:list"

View File

@@ -0,0 +1,195 @@
package probo
import (
"context"
"fmt"
"time"
"go.gearno.de/kit/pg"
"go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/page"
"go.probo.inc/probo/pkg/validator"
)
type (
ComplianceExternalURLService struct {
svc *TenantService
}
CreateComplianceExternalURLRequest struct {
TrustCenterID gid.GID
Name string
URL string
}
UpdateComplianceExternalURLRequest struct {
ID gid.GID
Name string
URL string
Rank *int
}
DeleteComplianceExternalURLRequest struct {
ID gid.GID
}
)
func (r *CreateComplianceExternalURLRequest) Validate() error {
v := validator.New()
v.Check(r.TrustCenterID, "trust_center_id", validator.Required(), validator.GID(coredata.TrustCenterEntityType))
v.Check(r.URL, "url", validator.Required(), validator.URL())
return v.Error()
}
func (r *UpdateComplianceExternalURLRequest) Validate() error {
v := validator.New()
v.Check(r.ID, "id", validator.Required(), validator.GID(coredata.ComplianceExternalURLEntityType))
v.Check(r.URL, "url", validator.Required(), validator.URL())
v.Check(r.Rank, "rank", validator.Min(1))
return v.Error()
}
func (r *DeleteComplianceExternalURLRequest) Validate() error {
v := validator.New()
v.Check(r.ID, "id", validator.Required(), validator.GID(coredata.ComplianceExternalURLEntityType))
return v.Error()
}
func (s ComplianceExternalURLService) List(
ctx context.Context,
trustCenterID gid.GID,
cursor *page.Cursor[coredata.ComplianceExternalURLOrderField],
) (*page.Page[*coredata.ComplianceExternalURL, coredata.ComplianceExternalURLOrderField], error) {
var items coredata.ComplianceExternalURLs
err := s.svc.pg.WithConn(
ctx,
func(conn pg.Conn) error {
if err := items.LoadByTrustCenterID(ctx, conn, s.svc.scope, trustCenterID, cursor); err != nil {
return fmt.Errorf("cannot load compliance external URLs: %w", err)
}
return nil
},
)
if err != nil {
return nil, err
}
return page.NewPage(items, cursor), nil
}
func (s ComplianceExternalURLService) Create(
ctx context.Context,
req *CreateComplianceExternalURLRequest,
) (*coredata.ComplianceExternalURL, error) {
if err := req.Validate(); err != nil {
return nil, err
}
now := time.Now()
id := gid.New(s.svc.scope.GetTenantID(), coredata.ComplianceExternalURLEntityType)
var item *coredata.ComplianceExternalURL
err := s.svc.pg.WithTx(
ctx,
func(tx pg.Conn) error {
trustCenter := &coredata.TrustCenter{}
if err := trustCenter.LoadByID(ctx, tx, s.svc.scope, req.TrustCenterID); err != nil {
return fmt.Errorf("cannot load trust center: %w", err)
}
item = &coredata.ComplianceExternalURL{
ID: id,
OrganizationID: trustCenter.OrganizationID,
TrustCenterID: req.TrustCenterID,
Name: req.Name,
URL: req.URL,
CreatedAt: now,
UpdatedAt: now,
}
if err := item.Insert(ctx, tx, s.svc.scope); err != nil {
return fmt.Errorf("cannot insert compliance external URL: %w", err)
}
return nil
},
)
if err != nil {
return nil, err
}
return item, nil
}
func (s ComplianceExternalURLService) Update(
ctx context.Context,
req *UpdateComplianceExternalURLRequest,
) (*coredata.ComplianceExternalURL, error) {
if err := req.Validate(); err != nil {
return nil, err
}
var item *coredata.ComplianceExternalURL
err := s.svc.pg.WithTx(
ctx,
func(tx pg.Conn) error {
item = &coredata.ComplianceExternalURL{}
if err := item.LoadByID(ctx, tx, s.svc.scope, req.ID); err != nil {
return fmt.Errorf("cannot load compliance external URL: %w", err)
}
item.Name = req.Name
item.URL = req.URL
item.UpdatedAt = time.Now()
if req.Rank != nil {
item.Rank = *req.Rank
if err := item.UpdateRank(ctx, tx, s.svc.scope); err != nil {
return fmt.Errorf("cannot update compliance external URL rank: %w", err)
}
}
if err := item.Update(ctx, tx, s.svc.scope); err != nil {
return fmt.Errorf("cannot update compliance external URL: %w", err)
}
return nil
},
)
if err != nil {
return nil, err
}
return item, nil
}
func (s ComplianceExternalURLService) Delete(
ctx context.Context,
req *DeleteComplianceExternalURLRequest,
) error {
if err := req.Validate(); err != nil {
return err
}
return s.svc.pg.WithTx(
ctx,
func(tx pg.Conn) error {
item := &coredata.ComplianceExternalURL{}
if err := item.LoadByID(ctx, tx, s.svc.scope, req.ID); err != nil {
return fmt.Errorf("cannot load compliance external URL: %w", err)
}
if err := item.Delete(ctx, tx, s.svc.scope); err != nil {
return fmt.Errorf("cannot delete compliance external URL: %w", err)
}
return nil
},
)
}

View File

@@ -101,6 +101,7 @@ type (
TrustCenterReferences *TrustCenterReferenceService
TrustCenterFiles *TrustCenterFileService
ComplianceFrameworks *ComplianceFrameworkService
ComplianceExternalURLs *ComplianceExternalURLService
Nonconformities *NonconformityService
Obligations *ObligationService
Snapshots *SnapshotService
@@ -226,6 +227,7 @@ func (s *Service) WithTenant(tenantID gid.TenantID) *TenantService {
tenantService.TrustCenterAccesses = &TrustCenterAccessService{svc: tenantService}
tenantService.TrustCenterReferences = &TrustCenterReferenceService{svc: tenantService}
tenantService.ComplianceFrameworks = &ComplianceFrameworkService{svc: tenantService}
tenantService.ComplianceExternalURLs = &ComplianceExternalURLService{svc: tenantService}
tenantService.TrustCenterFiles = &TrustCenterFileService{
svc: tenantService,
fileValidator: filevalidation.NewValidator(

View File

@@ -178,7 +178,6 @@ func (s TrustCenterService) Update(
if req.Slug != nil {
trustCenter.Slug = *req.Slug
}
trustCenter.UpdatedAt = time.Now()
if err := trustCenter.Update(ctx, conn, s.svc.scope); err != nil {

View File

@@ -1227,6 +1227,20 @@ enum TrustCenterReferenceOrderField
)
}
enum ComplianceExternalURLOrderField
@goModel(
model: "go.probo.inc/probo/pkg/coredata.ComplianceExternalURLOrderField"
) {
CREATED_AT
@goEnum(
value: "go.probo.inc/probo/pkg/coredata.ComplianceExternalURLOrderFieldCreatedAt"
)
RANK
@goEnum(
value: "go.probo.inc/probo/pkg/coredata.ComplianceExternalURLOrderFieldRank"
)
}
enum ComplianceFrameworkOrderField
@goModel(
model: "go.probo.inc/probo/pkg/coredata.ComplianceFrameworkOrderField"
@@ -1491,6 +1505,14 @@ input TrustCenterFileOrder
field: TrustCenterFileOrderField!
}
input ComplianceExternalURLOrder
@goModel(
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ComplianceExternalURLOrderBy"
) {
direction: OrderDirection!
field: ComplianceExternalURLOrderField!
}
input ComplianceFrameworkOrder
@goModel(
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ComplianceFrameworkOrderBy"
@@ -1658,6 +1680,14 @@ type TrustCenter implements Node {
orderBy: ComplianceFrameworkOrder
): ComplianceFrameworkConnection! @goField(forceResolver: true)
externalUrls(
first: Int
after: CursorKey
last: Int
before: CursorKey
orderBy: ComplianceExternalURLOrder
): ComplianceExternalURLConnection! @goField(forceResolver: true)
permission(action: String!): Boolean! @goField(forceResolver: true)
}
@@ -2882,6 +2912,30 @@ type ComplianceFrameworkEdge {
node: ComplianceFramework!
}
type ComplianceExternalURL implements Node {
id: ID!
name: String!
url: String!
rank: Int!
createdAt: Datetime!
updatedAt: Datetime!
permission(action: String!): Boolean! @goField(forceResolver: true)
}
type ComplianceExternalURLConnection
@goModel(
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ComplianceExternalURLConnection"
) {
edges: [ComplianceExternalURLEdge!]!
pageInfo: PageInfo!
}
type ComplianceExternalURLEdge {
cursor: CursorKey!
node: ComplianceExternalURL!
}
type TrustCenterFile implements Node {
id: ID!
name: String!
@@ -3330,6 +3384,15 @@ type Mutation {
deleteComplianceFramework(
input: DeleteComplianceFrameworkInput!
): DeleteComplianceFrameworkPayload!
createComplianceExternalURL(
input: CreateComplianceExternalURLInput!
): CreateComplianceExternalURLPayload!
updateComplianceExternalURL(
input: UpdateComplianceExternalURLInput!
): UpdateComplianceExternalURLPayload!
deleteComplianceExternalURL(
input: DeleteComplianceExternalURLInput!
): DeleteComplianceExternalURLPayload!
# Trust Center File mutations
createTrustCenterFile(
input: CreateTrustCenterFileInput!
@@ -3738,6 +3801,23 @@ input DeleteComplianceFrameworkInput {
id: ID!
}
input CreateComplianceExternalURLInput {
trustCenterId: ID!
name: String!
url: String!
}
input UpdateComplianceExternalURLInput {
id: ID!
name: String!
url: String!
rank: Int
}
input DeleteComplianceExternalURLInput {
id: ID!
}
input CreateTrustCenterFileInput {
organizationId: ID!
name: String!
@@ -4561,6 +4641,18 @@ type DeleteComplianceFrameworkPayload {
deletedComplianceFrameworkId: ID!
}
type CreateComplianceExternalURLPayload {
complianceExternalUrlEdge: ComplianceExternalURLEdge!
}
type UpdateComplianceExternalURLPayload {
complianceExternalUrl: ComplianceExternalURL!
}
type DeleteComplianceExternalURLPayload {
deletedComplianceExternalUrlId: ID!
}
type CreateTrustCenterFilePayload {
trustCenterFileEdge: TrustCenterFileEdge!
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,44 @@
package types
import (
"go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/page"
)
type ComplianceExternalURLOrderBy = OrderBy[coredata.ComplianceExternalURLOrderField]
type ComplianceExternalURLConnection struct {
Edges []*ComplianceExternalURLEdge `json:"edges"`
PageInfo *PageInfo `json:"pageInfo"`
}
func NewComplianceExternalURL(c *coredata.ComplianceExternalURL) *ComplianceExternalURL {
return &ComplianceExternalURL{
ID: c.ID,
Name: c.Name,
URL: c.URL,
Rank: c.Rank,
CreatedAt: c.CreatedAt,
UpdatedAt: c.UpdatedAt,
}
}
func NewComplianceExternalURLConnection(
p *page.Page[*coredata.ComplianceExternalURL, coredata.ComplianceExternalURLOrderField],
) *ComplianceExternalURLConnection {
edges := make([]*ComplianceExternalURLEdge, len(p.Data))
for i := range edges {
edges[i] = NewComplianceExternalURLEdge(p.Data[i], p.Cursor.OrderBy.Field)
}
return &ComplianceExternalURLConnection{
Edges: edges,
PageInfo: NewPageInfo(p),
}
}
func NewComplianceExternalURLEdge(c *coredata.ComplianceExternalURL, orderBy coredata.ComplianceExternalURLOrderField) *ComplianceExternalURLEdge {
return &ComplianceExternalURLEdge{
Cursor: c.CursorKey(orderBy),
Node: NewComplianceExternalURL(c),
}
}

View File

@@ -149,6 +149,24 @@ type CancelSignatureRequestPayload struct {
DeletedDocumentVersionSignatureID gid.GID `json:"deletedDocumentVersionSignatureId"`
}
type ComplianceExternalURL struct {
ID gid.GID `json:"id"`
Name string `json:"name"`
URL string `json:"url"`
Rank int `json:"rank"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
Permission bool `json:"permission"`
}
func (ComplianceExternalURL) IsNode() {}
func (this ComplianceExternalURL) GetID() gid.GID { return this.ID }
type ComplianceExternalURLEdge struct {
Cursor page.CursorKey `json:"cursor"`
Node *ComplianceExternalURL `json:"node"`
}
type ComplianceFrameworkEdge struct {
Cursor page.CursorKey `json:"cursor"`
Node *ComplianceFramework `json:"node"`
@@ -255,6 +273,16 @@ type CreateAuditPayload struct {
AuditEdge *AuditEdge `json:"auditEdge"`
}
type CreateComplianceExternalURLInput struct {
TrustCenterID gid.GID `json:"trustCenterId"`
Name string `json:"name"`
URL string `json:"url"`
}
type CreateComplianceExternalURLPayload struct {
ComplianceExternalURLEdge *ComplianceExternalURLEdge `json:"complianceExternalUrlEdge"`
}
type CreateComplianceFrameworkInput struct {
TrustCenterID gid.GID `json:"trustCenterId"`
FrameworkID gid.GID `json:"frameworkId"`
@@ -793,6 +821,14 @@ type DeleteAuditReportPayload struct {
Audit *Audit `json:"audit"`
}
type DeleteComplianceExternalURLInput struct {
ID gid.GID `json:"id"`
}
type DeleteComplianceExternalURLPayload struct {
DeletedComplianceExternalURLID gid.GID `json:"deletedComplianceExternalUrlId"`
}
type DeleteComplianceFrameworkInput struct {
ID gid.GID `json:"id"`
}
@@ -1864,19 +1900,20 @@ type TransferImpactAssessmentFilter struct {
}
type TrustCenter struct {
ID gid.GID `json:"id"`
Active bool `json:"active"`
LogoFileURL *string `json:"logoFileUrl,omitempty"`
DarkLogoFileURL *string `json:"darkLogoFileUrl,omitempty"`
NdaFileName *string `json:"ndaFileName,omitempty"`
NdaFileURL *string `json:"ndaFileUrl,omitempty"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
Organization *Organization `json:"organization"`
Accesses *TrustCenterAccessConnection `json:"accesses"`
References *TrustCenterReferenceConnection `json:"references"`
ComplianceFrameworks *ComplianceFrameworkConnection `json:"complianceFrameworks"`
Permission bool `json:"permission"`
ID gid.GID `json:"id"`
Active bool `json:"active"`
LogoFileURL *string `json:"logoFileUrl,omitempty"`
DarkLogoFileURL *string `json:"darkLogoFileUrl,omitempty"`
NdaFileName *string `json:"ndaFileName,omitempty"`
NdaFileURL *string `json:"ndaFileUrl,omitempty"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
Organization *Organization `json:"organization"`
Accesses *TrustCenterAccessConnection `json:"accesses"`
References *TrustCenterReferenceConnection `json:"references"`
ComplianceFrameworks *ComplianceFrameworkConnection `json:"complianceFrameworks"`
ExternalUrls *ComplianceExternalURLConnection `json:"externalUrls"`
Permission bool `json:"permission"`
}
func (TrustCenter) IsNode() {}
@@ -2006,6 +2043,17 @@ type UpdateAuditPayload struct {
Audit *Audit `json:"audit"`
}
type UpdateComplianceExternalURLInput struct {
ID gid.GID `json:"id"`
Name string `json:"name"`
URL string `json:"url"`
Rank *int `json:"rank,omitempty"`
}
type UpdateComplianceExternalURLPayload struct {
ComplianceExternalURL *ComplianceExternalURL `json:"complianceExternalUrl"`
}
type UpdateComplianceFrameworkInput struct {
ID gid.GID `json:"id"`
Rank int `json:"rank"`

View File

@@ -324,6 +324,11 @@ func (r *auditConnectionResolver) TotalCount(ctx context.Context, obj *types.Aud
return count, nil
}
// Permission is the resolver for the permission field.
func (r *complianceExternalURLResolver) Permission(ctx context.Context, obj *types.ComplianceExternalURL, action string) (bool, error) {
return r.Resolver.Permission(ctx, obj, action)
}
// Framework is the resolver for the framework field.
func (r *complianceFrameworkResolver) Framework(ctx context.Context, obj *types.ComplianceFramework) (*types.Framework, error) {
if err := r.authorize(ctx, obj.FrameworkID, probo.ActionFrameworkGet); err != nil {
@@ -2141,6 +2146,74 @@ func (r *mutationResolver) DeleteComplianceFramework(ctx context.Context, input
}, nil
}
// CreateComplianceExternalURL is the resolver for the createComplianceExternalURL field.
func (r *mutationResolver) CreateComplianceExternalURL(ctx context.Context, input types.CreateComplianceExternalURLInput) (*types.CreateComplianceExternalURLPayload, error) {
if err := r.authorize(ctx, input.TrustCenterID, probo.ActionComplianceExternalURLCreate); err != nil {
return nil, err
}
prb := r.ProboService(ctx, input.TrustCenterID.TenantID())
item, err := prb.ComplianceExternalURLs.Create(
ctx,
&probo.CreateComplianceExternalURLRequest{
TrustCenterID: input.TrustCenterID,
Name: input.Name,
URL: input.URL,
},
)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot create compliance external URL", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return &types.CreateComplianceExternalURLPayload{
ComplianceExternalURLEdge: types.NewComplianceExternalURLEdge(item, coredata.ComplianceExternalURLOrderFieldRank),
}, nil
}
// UpdateComplianceExternalURL is the resolver for the updateComplianceExternalURL field.
func (r *mutationResolver) UpdateComplianceExternalURL(ctx context.Context, input types.UpdateComplianceExternalURLInput) (*types.UpdateComplianceExternalURLPayload, error) {
if err := r.authorize(ctx, input.ID, probo.ActionComplianceExternalURLUpdate); err != nil {
return nil, err
}
prb := r.ProboService(ctx, input.ID.TenantID())
item, err := prb.ComplianceExternalURLs.Update(ctx, &probo.UpdateComplianceExternalURLRequest{
ID: input.ID,
Name: input.Name,
URL: input.URL,
Rank: input.Rank,
})
if err != nil {
r.logger.ErrorCtx(ctx, "cannot update compliance external URL", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return &types.UpdateComplianceExternalURLPayload{
ComplianceExternalURL: types.NewComplianceExternalURL(item),
}, nil
}
// DeleteComplianceExternalURL is the resolver for the deleteComplianceExternalURL field.
func (r *mutationResolver) DeleteComplianceExternalURL(ctx context.Context, input types.DeleteComplianceExternalURLInput) (*types.DeleteComplianceExternalURLPayload, error) {
if err := r.authorize(ctx, input.ID, probo.ActionComplianceExternalURLDelete); err != nil {
return nil, err
}
prb := r.ProboService(ctx, input.ID.TenantID())
if err := prb.ComplianceExternalURLs.Delete(ctx, &probo.DeleteComplianceExternalURLRequest{ID: input.ID}); err != nil {
r.logger.ErrorCtx(ctx, "cannot delete compliance external URL", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return &types.DeleteComplianceExternalURLPayload{
DeletedComplianceExternalURLID: input.ID,
}, nil
}
// CreateTrustCenterFile is the resolver for the createTrustCenterFile field.
func (r *mutationResolver) CreateTrustCenterFile(ctx context.Context, input types.CreateTrustCenterFileInput) (*types.CreateTrustCenterFilePayload, error) {
if err := r.authorize(ctx, input.OrganizationID, probo.ActionTrustCenterFileCreate); err != nil {
@@ -7887,6 +7960,36 @@ func (r *trustCenterResolver) ComplianceFrameworks(ctx context.Context, obj *typ
return types.NewComplianceFrameworkConnection(result), nil
}
// ExternalUrls is the resolver for the externalUrls field.
func (r *trustCenterResolver) ExternalUrls(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.OrderBy[coredata.ComplianceExternalURLOrderField]) (*types.ComplianceExternalURLConnection, error) {
if err := r.authorize(ctx, obj.ID, probo.ActionComplianceExternalURLList); err != nil {
return nil, err
}
prb := r.ProboService(ctx, obj.ID.TenantID())
pageOrderBy := page.OrderBy[coredata.ComplianceExternalURLOrderField]{
Field: coredata.ComplianceExternalURLOrderFieldRank,
Direction: page.OrderDirectionAsc,
}
if orderBy != nil {
pageOrderBy = page.OrderBy[coredata.ComplianceExternalURLOrderField]{
Field: orderBy.Field,
Direction: orderBy.Direction,
}
}
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
result, err := prb.ComplianceExternalURLs.List(ctx, obj.ID, cursor)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot list compliance external URLs", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return types.NewComplianceExternalURLConnection(result), nil
}
// Permission is the resolver for the permission field.
func (r *trustCenterResolver) Permission(ctx context.Context, obj *types.TrustCenter, action string) (bool, error) {
return r.Resolver.Permission(ctx, obj, action)
@@ -8868,6 +8971,11 @@ func (r *Resolver) AuditConnection() schema.AuditConnectionResolver {
return &auditConnectionResolver{r}
}
// ComplianceExternalURL returns schema.ComplianceExternalURLResolver implementation.
func (r *Resolver) ComplianceExternalURL() schema.ComplianceExternalURLResolver {
return &complianceExternalURLResolver{r}
}
// ComplianceFramework returns schema.ComplianceFrameworkResolver implementation.
func (r *Resolver) ComplianceFramework() schema.ComplianceFrameworkResolver {
return &complianceFrameworkResolver{r}
@@ -9175,6 +9283,7 @@ type assetResolver struct{ *Resolver }
type assetConnectionResolver struct{ *Resolver }
type auditResolver struct{ *Resolver }
type auditConnectionResolver struct{ *Resolver }
type complianceExternalURLResolver struct{ *Resolver }
type complianceFrameworkResolver struct{ *Resolver }
type continualImprovementResolver struct{ *Resolver }
type continualImprovementConnectionResolver struct{ *Resolver }

View File

@@ -573,6 +573,30 @@ type TrustCenter implements Node {
last: Int
before: CursorKey
): ComplianceFrameworkConnection! @goField(forceResolver: true)
externalUrls(
first: Int
after: CursorKey
last: Int
before: CursorKey
): ComplianceExternalURLConnection! @goField(forceResolver: true)
}
type ComplianceExternalURL implements Node {
id: ID!
name: String!
url: String!
rank: Int!
}
type ComplianceExternalURLConnection {
edges: [ComplianceExternalURLEdge!]!
pageInfo: PageInfo!
}
type ComplianceExternalURLEdge {
cursor: CursorKey!
node: ComplianceExternalURL!
}
type TrustCenterAccess implements Node {

View File

@@ -78,6 +78,23 @@ type ComplexityRoot struct {
Node func(childComplexity int) int
}
ComplianceExternalURL struct {
ID func(childComplexity int) int
Name func(childComplexity int) int
Rank func(childComplexity int) int
URL func(childComplexity int) int
}
ComplianceExternalURLConnection struct {
Edges func(childComplexity int) int
PageInfo func(childComplexity int) int
}
ComplianceExternalURLEdge struct {
Cursor func(childComplexity int) int
Node func(childComplexity int) int
}
ComplianceFramework struct {
Framework func(childComplexity int) int
ID func(childComplexity int) int
@@ -236,6 +253,7 @@ type ComplexityRoot struct {
ComplianceFrameworks func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int
DarkLogoFileURL func(childComplexity int) int
Documents func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int
ExternalUrls func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int
ID func(childComplexity int) int
LogoFileURL func(childComplexity int) int
NonDisclosureAgreement func(childComplexity int) int
@@ -376,6 +394,7 @@ type TrustCenterResolver interface {
References(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.TrustCenterReferenceConnection, error)
TrustCenterFiles(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.TrustCenterFileConnection, error)
ComplianceFrameworks(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.ComplianceFrameworkConnection, error)
ExternalUrls(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.ComplianceExternalURLConnection, error)
}
type TrustCenterFileResolver interface {
IsUserAuthorized(ctx context.Context, obj *types.TrustCenterFile) (bool, error)
@@ -460,6 +479,57 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
return e.ComplexityRoot.AuditEdge.Node(childComplexity), true
case "ComplianceExternalURL.id":
if e.ComplexityRoot.ComplianceExternalURL.ID == nil {
break
}
return e.ComplexityRoot.ComplianceExternalURL.ID(childComplexity), true
case "ComplianceExternalURL.name":
if e.ComplexityRoot.ComplianceExternalURL.Name == nil {
break
}
return e.ComplexityRoot.ComplianceExternalURL.Name(childComplexity), true
case "ComplianceExternalURL.rank":
if e.ComplexityRoot.ComplianceExternalURL.Rank == nil {
break
}
return e.ComplexityRoot.ComplianceExternalURL.Rank(childComplexity), true
case "ComplianceExternalURL.url":
if e.ComplexityRoot.ComplianceExternalURL.URL == nil {
break
}
return e.ComplexityRoot.ComplianceExternalURL.URL(childComplexity), true
case "ComplianceExternalURLConnection.edges":
if e.ComplexityRoot.ComplianceExternalURLConnection.Edges == nil {
break
}
return e.ComplexityRoot.ComplianceExternalURLConnection.Edges(childComplexity), true
case "ComplianceExternalURLConnection.pageInfo":
if e.ComplexityRoot.ComplianceExternalURLConnection.PageInfo == nil {
break
}
return e.ComplexityRoot.ComplianceExternalURLConnection.PageInfo(childComplexity), true
case "ComplianceExternalURLEdge.cursor":
if e.ComplexityRoot.ComplianceExternalURLEdge.Cursor == nil {
break
}
return e.ComplexityRoot.ComplianceExternalURLEdge.Cursor(childComplexity), true
case "ComplianceExternalURLEdge.node":
if e.ComplexityRoot.ComplianceExternalURLEdge.Node == nil {
break
}
return e.ComplexityRoot.ComplianceExternalURLEdge.Node(childComplexity), true
case "ComplianceFramework.framework":
if e.ComplexityRoot.ComplianceFramework.Framework == nil {
break
@@ -1053,6 +1123,17 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
}
return e.ComplexityRoot.TrustCenter.Documents(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey)), true
case "TrustCenter.externalUrls":
if e.ComplexityRoot.TrustCenter.ExternalUrls == nil {
break
}
args, err := ec.field_TrustCenter_externalUrls_args(ctx, rawArgs)
if err != nil {
return 0, false
}
return e.ComplexityRoot.TrustCenter.ExternalUrls(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey)), true
case "TrustCenter.id":
if e.ComplexityRoot.TrustCenter.ID == nil {
break
@@ -2020,6 +2101,30 @@ type TrustCenter implements Node {
last: Int
before: CursorKey
): ComplianceFrameworkConnection! @goField(forceResolver: true)
externalUrls(
first: Int
after: CursorKey
last: Int
before: CursorKey
): ComplianceExternalURLConnection! @goField(forceResolver: true)
}
type ComplianceExternalURL implements Node {
id: ID!
name: String!
url: String!
rank: Int!
}
type ComplianceExternalURLConnection {
edges: [ComplianceExternalURLEdge!]!
pageInfo: PageInfo!
}
type ComplianceExternalURLEdge {
cursor: CursorKey!
node: ComplianceExternalURL!
}
type TrustCenterAccess implements Node {
@@ -2595,6 +2700,32 @@ func (ec *executionContext) field_TrustCenter_documents_args(ctx context.Context
return args, nil
}
func (ec *executionContext) field_TrustCenter_externalUrls_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, "first", ec.unmarshalOInt2ᚖint)
if err != nil {
return nil, err
}
args["first"] = arg0
arg1, err := graphql.ProcessArgField(ctx, rawArgs, "after", ec.unmarshalOCursorKey2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋpageᚐCursorKey)
if err != nil {
return nil, err
}
args["after"] = arg1
arg2, err := graphql.ProcessArgField(ctx, rawArgs, "last", ec.unmarshalOInt2ᚖint)
if err != nil {
return nil, err
}
args["last"] = arg2
arg3, err := graphql.ProcessArgField(ctx, rawArgs, "before", ec.unmarshalOCursorKey2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋpageᚐCursorKey)
if err != nil {
return nil, err
}
args["before"] = arg3
return args, nil
}
func (ec *executionContext) field_TrustCenter_references_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error
args := map[string]any{}
@@ -3102,6 +3233,264 @@ func (ec *executionContext) fieldContext_AuditEdge_node(_ context.Context, field
return fc, nil
}
func (ec *executionContext) _ComplianceExternalURL_id(ctx context.Context, field graphql.CollectedField, obj *types.ComplianceExternalURL) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_ComplianceExternalURL_id,
func(ctx context.Context) (any, error) {
return obj.ID, nil
},
nil,
ec.marshalNID2goᚗproboᚗincᚋproboᚋpkgᚋgidᚐGID,
true,
true,
)
}
func (ec *executionContext) fieldContext_ComplianceExternalURL_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "ComplianceExternalURL",
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) _ComplianceExternalURL_name(ctx context.Context, field graphql.CollectedField, obj *types.ComplianceExternalURL) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_ComplianceExternalURL_name,
func(ctx context.Context) (any, error) {
return obj.Name, nil
},
nil,
ec.marshalNString2string,
true,
true,
)
}
func (ec *executionContext) fieldContext_ComplianceExternalURL_name(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "ComplianceExternalURL",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type String does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _ComplianceExternalURL_url(ctx context.Context, field graphql.CollectedField, obj *types.ComplianceExternalURL) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_ComplianceExternalURL_url,
func(ctx context.Context) (any, error) {
return obj.URL, nil
},
nil,
ec.marshalNString2string,
true,
true,
)
}
func (ec *executionContext) fieldContext_ComplianceExternalURL_url(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "ComplianceExternalURL",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type String does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _ComplianceExternalURL_rank(ctx context.Context, field graphql.CollectedField, obj *types.ComplianceExternalURL) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_ComplianceExternalURL_rank,
func(ctx context.Context) (any, error) {
return obj.Rank, nil
},
nil,
ec.marshalNInt2int,
true,
true,
)
}
func (ec *executionContext) fieldContext_ComplianceExternalURL_rank(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "ComplianceExternalURL",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type Int does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _ComplianceExternalURLConnection_edges(ctx context.Context, field graphql.CollectedField, obj *types.ComplianceExternalURLConnection) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_ComplianceExternalURLConnection_edges,
func(ctx context.Context) (any, error) {
return obj.Edges, nil
},
nil,
ec.marshalNComplianceExternalURLEdge2ᚕᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐComplianceExternalURLEdgeᚄ,
true,
true,
)
}
func (ec *executionContext) fieldContext_ComplianceExternalURLConnection_edges(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "ComplianceExternalURLConnection",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "cursor":
return ec.fieldContext_ComplianceExternalURLEdge_cursor(ctx, field)
case "node":
return ec.fieldContext_ComplianceExternalURLEdge_node(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type ComplianceExternalURLEdge", field.Name)
},
}
return fc, nil
}
func (ec *executionContext) _ComplianceExternalURLConnection_pageInfo(ctx context.Context, field graphql.CollectedField, obj *types.ComplianceExternalURLConnection) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_ComplianceExternalURLConnection_pageInfo,
func(ctx context.Context) (any, error) {
return obj.PageInfo, nil
},
nil,
ec.marshalNPageInfo2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐPageInfo,
true,
true,
)
}
func (ec *executionContext) fieldContext_ComplianceExternalURLConnection_pageInfo(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "ComplianceExternalURLConnection",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "hasNextPage":
return ec.fieldContext_PageInfo_hasNextPage(ctx, field)
case "hasPreviousPage":
return ec.fieldContext_PageInfo_hasPreviousPage(ctx, field)
case "startCursor":
return ec.fieldContext_PageInfo_startCursor(ctx, field)
case "endCursor":
return ec.fieldContext_PageInfo_endCursor(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type PageInfo", field.Name)
},
}
return fc, nil
}
func (ec *executionContext) _ComplianceExternalURLEdge_cursor(ctx context.Context, field graphql.CollectedField, obj *types.ComplianceExternalURLEdge) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_ComplianceExternalURLEdge_cursor,
func(ctx context.Context) (any, error) {
return obj.Cursor, nil
},
nil,
ec.marshalNCursorKey2goᚗproboᚗincᚋproboᚋpkgᚋpageᚐCursorKey,
true,
true,
)
}
func (ec *executionContext) fieldContext_ComplianceExternalURLEdge_cursor(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "ComplianceExternalURLEdge",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type CursorKey does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _ComplianceExternalURLEdge_node(ctx context.Context, field graphql.CollectedField, obj *types.ComplianceExternalURLEdge) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_ComplianceExternalURLEdge_node,
func(ctx context.Context) (any, error) {
return obj.Node, nil
},
nil,
ec.marshalNComplianceExternalURL2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐComplianceExternalURL,
true,
true,
)
}
func (ec *executionContext) fieldContext_ComplianceExternalURLEdge_node(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "ComplianceExternalURLEdge",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "id":
return ec.fieldContext_ComplianceExternalURL_id(ctx, field)
case "name":
return ec.fieldContext_ComplianceExternalURL_name(ctx, field)
case "url":
return ec.fieldContext_ComplianceExternalURL_url(ctx, field)
case "rank":
return ec.fieldContext_ComplianceExternalURL_rank(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type ComplianceExternalURL", field.Name)
},
}
return fc, nil
}
func (ec *executionContext) _ComplianceFramework_id(ctx context.Context, field graphql.CollectedField, obj *types.ComplianceFramework) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
@@ -5660,6 +6049,8 @@ func (ec *executionContext) fieldContext_Query_currentTrustCenter(_ context.Cont
return ec.fieldContext_TrustCenter_trustCenterFiles(ctx, field)
case "complianceFrameworks":
return ec.fieldContext_TrustCenter_complianceFrameworks(ctx, field)
case "externalUrls":
return ec.fieldContext_TrustCenter_externalUrls(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type TrustCenter", field.Name)
},
@@ -6732,6 +7123,53 @@ func (ec *executionContext) fieldContext_TrustCenter_complianceFrameworks(ctx co
return fc, nil
}
func (ec *executionContext) _TrustCenter_externalUrls(ctx context.Context, field graphql.CollectedField, obj *types.TrustCenter) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_TrustCenter_externalUrls,
func(ctx context.Context) (any, error) {
fc := graphql.GetFieldContext(ctx)
return ec.Resolvers.TrustCenter().ExternalUrls(ctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*page.CursorKey), fc.Args["last"].(*int), fc.Args["before"].(*page.CursorKey))
},
nil,
ec.marshalNComplianceExternalURLConnection2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐComplianceExternalURLConnection,
true,
true,
)
}
func (ec *executionContext) fieldContext_TrustCenter_externalUrls(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "TrustCenter",
Field: field,
IsMethod: true,
IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "edges":
return ec.fieldContext_ComplianceExternalURLConnection_edges(ctx, field)
case "pageInfo":
return ec.fieldContext_ComplianceExternalURLConnection_pageInfo(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type ComplianceExternalURLConnection", field.Name)
},
}
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_TrustCenter_externalUrls_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
ec.Error(ctx, err)
return fc, err
}
return fc, nil
}
func (ec *executionContext) _TrustCenterAccess_id(ctx context.Context, field graphql.CollectedField, obj *types.TrustCenterAccess) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
@@ -9822,6 +10260,13 @@ func (ec *executionContext) _Node(ctx context.Context, sel ast.SelectionSet, obj
return graphql.Null
}
return ec._ComplianceFramework(ctx, sel, obj)
case types.ComplianceExternalURL:
return ec._ComplianceExternalURL(ctx, sel, &obj)
case *types.ComplianceExternalURL:
if obj == nil {
return graphql.Null
}
return ec._ComplianceExternalURL(ctx, sel, obj)
case types.Audit:
return ec._Audit(ctx, sel, &obj)
case *types.Audit:
@@ -10079,6 +10524,148 @@ func (ec *executionContext) _AuditEdge(ctx context.Context, sel ast.SelectionSet
return out
}
var complianceExternalURLImplementors = []string{"ComplianceExternalURL", "Node"}
func (ec *executionContext) _ComplianceExternalURL(ctx context.Context, sel ast.SelectionSet, obj *types.ComplianceExternalURL) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, complianceExternalURLImplementors)
out := graphql.NewFieldSet(fields)
deferred := make(map[string]*graphql.FieldSet)
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("ComplianceExternalURL")
case "id":
out.Values[i] = ec._ComplianceExternalURL_id(ctx, field, obj)
if out.Values[i] == graphql.Null {
out.Invalids++
}
case "name":
out.Values[i] = ec._ComplianceExternalURL_name(ctx, field, obj)
if out.Values[i] == graphql.Null {
out.Invalids++
}
case "url":
out.Values[i] = ec._ComplianceExternalURL_url(ctx, field, obj)
if out.Values[i] == graphql.Null {
out.Invalids++
}
case "rank":
out.Values[i] = ec._ComplianceExternalURL_rank(ctx, field, obj)
if out.Values[i] == graphql.Null {
out.Invalids++
}
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch(ctx)
if out.Invalids > 0 {
return graphql.Null
}
atomic.AddInt32(&ec.Deferred, int32(len(deferred)))
for label, dfs := range deferred {
ec.ProcessDeferredGroup(graphql.DeferredGroup{
Label: label,
Path: graphql.GetPath(ctx),
FieldSet: dfs,
Context: ctx,
})
}
return out
}
var complianceExternalURLConnectionImplementors = []string{"ComplianceExternalURLConnection"}
func (ec *executionContext) _ComplianceExternalURLConnection(ctx context.Context, sel ast.SelectionSet, obj *types.ComplianceExternalURLConnection) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, complianceExternalURLConnectionImplementors)
out := graphql.NewFieldSet(fields)
deferred := make(map[string]*graphql.FieldSet)
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("ComplianceExternalURLConnection")
case "edges":
out.Values[i] = ec._ComplianceExternalURLConnection_edges(ctx, field, obj)
if out.Values[i] == graphql.Null {
out.Invalids++
}
case "pageInfo":
out.Values[i] = ec._ComplianceExternalURLConnection_pageInfo(ctx, field, obj)
if out.Values[i] == graphql.Null {
out.Invalids++
}
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch(ctx)
if out.Invalids > 0 {
return graphql.Null
}
atomic.AddInt32(&ec.Deferred, int32(len(deferred)))
for label, dfs := range deferred {
ec.ProcessDeferredGroup(graphql.DeferredGroup{
Label: label,
Path: graphql.GetPath(ctx),
FieldSet: dfs,
Context: ctx,
})
}
return out
}
var complianceExternalURLEdgeImplementors = []string{"ComplianceExternalURLEdge"}
func (ec *executionContext) _ComplianceExternalURLEdge(ctx context.Context, sel ast.SelectionSet, obj *types.ComplianceExternalURLEdge) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, complianceExternalURLEdgeImplementors)
out := graphql.NewFieldSet(fields)
deferred := make(map[string]*graphql.FieldSet)
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("ComplianceExternalURLEdge")
case "cursor":
out.Values[i] = ec._ComplianceExternalURLEdge_cursor(ctx, field, obj)
if out.Values[i] == graphql.Null {
out.Invalids++
}
case "node":
out.Values[i] = ec._ComplianceExternalURLEdge_node(ctx, field, obj)
if out.Values[i] == graphql.Null {
out.Invalids++
}
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch(ctx)
if out.Invalids > 0 {
return graphql.Null
}
atomic.AddInt32(&ec.Deferred, int32(len(deferred)))
for label, dfs := range deferred {
ec.ProcessDeferredGroup(graphql.DeferredGroup{
Label: label,
Path: graphql.GetPath(ctx),
FieldSet: dfs,
Context: ctx,
})
}
return out
}
var complianceFrameworkImplementors = []string{"ComplianceFramework", "Node"}
func (ec *executionContext) _ComplianceFramework(ctx context.Context, sel ast.SelectionSet, obj *types.ComplianceFramework) graphql.Marshaler {
@@ -12027,6 +12614,42 @@ func (ec *executionContext) _TrustCenter(ctx context.Context, sel ast.SelectionS
continue
}
out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
case "externalUrls":
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._TrustCenter_externalUrls(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))
@@ -13156,6 +13779,56 @@ func (ec *executionContext) marshalNBoolean2bool(ctx context.Context, sel ast.Se
return res
}
func (ec *executionContext) marshalNComplianceExternalURL2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐComplianceExternalURL(ctx context.Context, sel ast.SelectionSet, v *types.ComplianceExternalURL) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow")
}
return graphql.Null
}
return ec._ComplianceExternalURL(ctx, sel, v)
}
func (ec *executionContext) marshalNComplianceExternalURLConnection2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐComplianceExternalURLConnection(ctx context.Context, sel ast.SelectionSet, v types.ComplianceExternalURLConnection) graphql.Marshaler {
return ec._ComplianceExternalURLConnection(ctx, sel, &v)
}
func (ec *executionContext) marshalNComplianceExternalURLConnection2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐComplianceExternalURLConnection(ctx context.Context, sel ast.SelectionSet, v *types.ComplianceExternalURLConnection) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow")
}
return graphql.Null
}
return ec._ComplianceExternalURLConnection(ctx, sel, v)
}
func (ec *executionContext) marshalNComplianceExternalURLEdge2ᚕᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐComplianceExternalURLEdgeᚄ(ctx context.Context, sel ast.SelectionSet, v []*types.ComplianceExternalURLEdge) graphql.Marshaler {
ret := graphql.MarshalSliceConcurrently(ctx, len(v), 0, false, func(ctx context.Context, i int) graphql.Marshaler {
fc := graphql.GetFieldContext(ctx)
fc.Result = &v[i]
return ec.marshalNComplianceExternalURLEdge2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐComplianceExternalURLEdge(ctx, sel, v[i])
})
for _, e := range ret {
if e == graphql.Null {
return graphql.Null
}
}
return ret
}
func (ec *executionContext) marshalNComplianceExternalURLEdge2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐComplianceExternalURLEdge(ctx context.Context, sel ast.SelectionSet, v *types.ComplianceExternalURLEdge) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow")
}
return graphql.Null
}
return ec._ComplianceExternalURLEdge(ctx, sel, v)
}
func (ec *executionContext) marshalNComplianceFramework2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐComplianceFramework(ctx context.Context, sel ast.SelectionSet, v *types.ComplianceFramework) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {

View File

@@ -0,0 +1,51 @@
// 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 types
import (
"go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/page"
)
func NewComplianceExternalURL(c *coredata.ComplianceExternalURL) *ComplianceExternalURL {
return &ComplianceExternalURL{
ID: c.ID,
Name: c.Name,
URL: c.URL,
Rank: c.Rank,
}
}
func NewComplianceExternalURLConnection(
p *page.Page[*coredata.ComplianceExternalURL, coredata.ComplianceExternalURLOrderField],
) *ComplianceExternalURLConnection {
edges := make([]*ComplianceExternalURLEdge, len(p.Data))
for i, item := range p.Data {
edges[i] = NewComplianceExternalURLEdge(item, p.Cursor.OrderBy.Field)
}
return &ComplianceExternalURLConnection{
Edges: edges,
PageInfo: NewPageInfo(p),
}
}
func NewComplianceExternalURLEdge(c *coredata.ComplianceExternalURL, orderBy coredata.ComplianceExternalURLOrderField) *ComplianceExternalURLEdge {
return &ComplianceExternalURLEdge{
Cursor: c.CursorKey(orderBy),
Node: NewComplianceExternalURL(c),
}
}

View File

@@ -44,6 +44,26 @@ type AuditEdge struct {
Node *Audit `json:"node"`
}
type ComplianceExternalURL struct {
ID gid.GID `json:"id"`
Name string `json:"name"`
URL string `json:"url"`
Rank int `json:"rank"`
}
func (ComplianceExternalURL) IsNode() {}
func (this ComplianceExternalURL) GetID() gid.GID { return this.ID }
type ComplianceExternalURLConnection struct {
Edges []*ComplianceExternalURLEdge `json:"edges"`
PageInfo *PageInfo `json:"pageInfo"`
}
type ComplianceExternalURLEdge struct {
Cursor page.CursorKey `json:"cursor"`
Node *ComplianceExternalURL `json:"node"`
}
type Document struct {
ID gid.GID `json:"id"`
Title string `json:"title"`
@@ -222,19 +242,20 @@ type SendMagicLinkPayload struct {
}
type TrustCenter struct {
ID gid.GID `json:"id"`
Active bool `json:"active"`
Slug string `json:"slug"`
LogoFileURL *string `json:"logoFileUrl,omitempty"`
DarkLogoFileURL *string `json:"darkLogoFileUrl,omitempty"`
NonDisclosureAgreement *NonDisclosureAgreement `json:"nonDisclosureAgreement,omitempty"`
Organization *Organization `json:"organization"`
Documents *DocumentConnection `json:"documents"`
Audits *AuditConnection `json:"audits"`
Vendors *VendorConnection `json:"vendors"`
References *TrustCenterReferenceConnection `json:"references"`
TrustCenterFiles *TrustCenterFileConnection `json:"trustCenterFiles"`
ComplianceFrameworks *ComplianceFrameworkConnection `json:"complianceFrameworks"`
ID gid.GID `json:"id"`
Active bool `json:"active"`
Slug string `json:"slug"`
LogoFileURL *string `json:"logoFileUrl,omitempty"`
DarkLogoFileURL *string `json:"darkLogoFileUrl,omitempty"`
NonDisclosureAgreement *NonDisclosureAgreement `json:"nonDisclosureAgreement,omitempty"`
Organization *Organization `json:"organization"`
Documents *DocumentConnection `json:"documents"`
Audits *AuditConnection `json:"audits"`
Vendors *VendorConnection `json:"vendors"`
References *TrustCenterReferenceConnection `json:"references"`
TrustCenterFiles *TrustCenterFileConnection `json:"trustCenterFiles"`
ComplianceFrameworks *ComplianceFrameworkConnection `json:"complianceFrameworks"`
ExternalUrls *ComplianceExternalURLConnection `json:"externalUrls"`
}
func (TrustCenter) IsNode() {}

View File

@@ -1108,6 +1108,25 @@ func (r *trustCenterResolver) ComplianceFrameworks(ctx context.Context, obj *typ
return types.NewComplianceFrameworkConnection(cfPage), nil
}
// ExternalUrls is the resolver for the externalUrls field.
func (r *trustCenterResolver) ExternalUrls(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.ComplianceExternalURLConnection, error) {
trustService := r.TrustService(ctx, obj.ID.TenantID())
pageOrderBy := page.OrderBy[coredata.ComplianceExternalURLOrderField]{
Field: coredata.ComplianceExternalURLOrderFieldRank,
Direction: page.OrderDirectionAsc,
}
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
result, err := trustService.ComplianceExternalURLs.ListForTrustCenterID(ctx, obj.ID, cursor)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot list compliance external URLs", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return types.NewComplianceExternalURLConnection(result), nil
}
// IsUserAuthorized is the resolver for the isUserAuthorized field.
func (r *trustCenterFileResolver) IsUserAuthorized(ctx context.Context, obj *types.TrustCenterFile) (bool, error) {
trustService := r.TrustService(ctx, obj.ID.TenantID())

View File

@@ -0,0 +1,55 @@
// 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 trust
import (
"context"
"fmt"
"go.gearno.de/kit/pg"
"go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/page"
)
type ComplianceExternalURLService struct {
svc *TenantService
}
func (s ComplianceExternalURLService) ListForTrustCenterID(
ctx context.Context,
trustCenterID gid.GID,
cursor *page.Cursor[coredata.ComplianceExternalURLOrderField],
) (*page.Page[*coredata.ComplianceExternalURL, coredata.ComplianceExternalURLOrderField], error) {
var items coredata.ComplianceExternalURLs
err := s.svc.pg.WithConn(
ctx,
func(conn pg.Conn) error {
err := items.LoadByTrustCenterID(ctx, conn, s.svc.scope, trustCenterID, cursor)
if err != nil {
return fmt.Errorf("cannot load compliance external URLs: %w", err)
}
return nil
},
)
if err != nil {
return nil, err
}
return page.NewPage(items, cursor), nil
}

View File

@@ -50,29 +50,30 @@ type (
}
TenantService struct {
pg *pg.Client
s3 *s3.Client
bucket string
scope coredata.Scoper
proboSvc *probo.Service
baseURL string
iam *iam.Service
esign *esign.Service
html2pdfConverter *html2pdf.Converter
fileManager *filemanager.Service
logger *log.Logger
TrustCenters *TrustCenterService
Documents *DocumentService
Audits *AuditService
Vendors *VendorService
Frameworks *FrameworkService
ComplianceFrameworks *ComplianceFrameworkService
TrustCenterAccesses *TrustCenterAccessService
TrustCenterReferences *TrustCenterReferenceService
TrustCenterFiles *TrustCenterFileService
Reports *ReportService
Organizations *OrganizationService
SlackMessages *slack.SlackMessageService
pg *pg.Client
s3 *s3.Client
bucket string
scope coredata.Scoper
proboSvc *probo.Service
baseURL string
iam *iam.Service
esign *esign.Service
html2pdfConverter *html2pdf.Converter
fileManager *filemanager.Service
logger *log.Logger
TrustCenters *TrustCenterService
Documents *DocumentService
Audits *AuditService
Vendors *VendorService
Frameworks *FrameworkService
ComplianceFrameworks *ComplianceFrameworkService
TrustCenterAccesses *TrustCenterAccessService
TrustCenterReferences *TrustCenterReferenceService
TrustCenterFiles *TrustCenterFileService
Reports *ReportService
Organizations *OrganizationService
ComplianceExternalURLs *ComplianceExternalURLService
SlackMessages *slack.SlackMessageService
}
)
@@ -130,6 +131,7 @@ func (s *Service) WithTenant(tenantID gid.TenantID) *TenantService {
tenantService.TrustCenterFiles = &TrustCenterFileService{svc: tenantService}
tenantService.Reports = &ReportService{svc: tenantService}
tenantService.Organizations = &OrganizationService{svc: tenantService}
tenantService.ComplianceExternalURLs = &ComplianceExternalURLService{svc: tenantService}
tenantService.SlackMessages = s.slack.WithTenant(tenantID).SlackMessages
return tenantService