Refatcor policies management
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
3
apps/console/public/icons/arrow-inbox.svg
Normal file
3
apps/console/public/icons/arrow-inbox.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6 0C6.36819 0 6.66667 0.298477 6.66667 0.666667V6.05719L7.86193 4.86193C8.12228 4.60158 8.54439 4.60158 8.80474 4.86193C9.06509 5.12228 9.06509 5.54439 8.80474 5.80474L6.4714 8.13807C6.21106 8.39842 5.78895 8.39842 5.5286 8.13807L3.19526 5.80474C2.93491 5.54439 2.93491 5.12228 3.19526 4.86193C3.45561 4.60158 3.87772 4.60158 4.13807 4.86193L5.33333 6.05719V0.666667C5.33333 0.298477 5.63181 0 6 0ZM0.666667 7.33333C1.03486 7.33333 1.33333 7.63181 1.33333 8V9.33333C1.33333 10.0697 1.93028 10.6667 2.66667 10.6667H9.33333C10.0697 10.6667 10.6667 10.0697 10.6667 9.33333V8C10.6667 7.63181 10.9651 7.33333 11.3333 7.33333C11.7015 7.33333 12 7.63181 12 8V9.33333C12 10.8061 10.8061 12 9.33333 12H2.66667C1.19392 12 0 10.8061 0 9.33333V8C0 7.63181 0.298477 7.33333 0.666667 7.33333Z" fill="#141E12"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 950 B |
3
apps/console/public/icons/clock.svg
Normal file
3
apps/console/public/icons/clock.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.99966 1.39993C3.90702 1.39993 1.39993 3.90702 1.39993 6.99966C1.39993 10.0923 3.90701 12.5994 6.99966 12.5994C10.0923 12.5994 12.5994 10.0923 12.5994 6.99966C12.5994 3.90701 10.0923 1.39993 6.99966 1.39993ZM0 6.99966C0 3.13386 3.13386 0 6.99966 0C10.8655 0 13.9993 3.13386 13.9993 6.99966C13.9993 10.8655 10.8655 13.9993 6.99966 13.9993C3.13386 13.9993 0 10.8655 0 6.99966ZM7.69962 3.49983V6.70972L9.73947 8.74957L8.74957 9.73947L6.29969 7.28959V3.49983H7.69962Z" fill="#141E12"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 636 B |
5
apps/console/public/icons/dot-grid-1x3-horizontal.svg
Normal file
5
apps/console/public/icons/dot-grid-1x3-horizontal.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="14" height="3" viewBox="0 0 14 3" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.59993 1.39998C5.59993 0.626783 6.22671 0 6.99991 0C7.77311 0 8.3999 0.626783 8.3999 1.39998C8.3999 2.17318 7.77311 2.79997 6.99991 2.79997C6.22671 2.79997 5.59993 2.17318 5.59993 1.39998Z" fill="#141E12"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.1999 1.39998C11.1999 0.626783 11.8266 0 12.5998 0C13.373 0 13.9998 0.626783 13.9998 1.39998C13.9998 2.17318 13.373 2.79997 12.5998 2.79997C11.8266 2.79997 11.1999 2.17318 11.1999 1.39998Z" fill="#141E12"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 1.39998C0 0.626774 0.626806 0 1.39998 0C2.17316 0 2.79997 0.626774 2.79997 1.39998C2.79997 2.17319 2.17316 2.79997 1.39998 2.79997C0.626806 2.79997 0 2.17319 0 1.39998Z" fill="#141E12"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 857 B |
5
apps/console/public/images/dot-grid-1x3-horizontal.svg
Normal file
5
apps/console/public/images/dot-grid-1x3-horizontal.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="14" height="3" viewBox="0 0 14 3" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.59993 1.39998C5.59993 0.626783 6.22671 0 6.99991 0C7.77311 0 8.3999 0.626783 8.3999 1.39998C8.3999 2.17318 7.77311 2.79997 6.99991 2.79997C6.22671 2.79997 5.59993 2.17318 5.59993 1.39998Z" fill="#141E12"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.1999 1.39998C11.1999 0.626783 11.8266 0 12.5998 0C13.373 0 13.9998 0.626783 13.9998 1.39998C13.9998 2.17318 13.373 2.79997 12.5998 2.79997C11.8266 2.79997 11.1999 2.17318 11.1999 1.39998Z" fill="#141E12"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 1.39998C0 0.626774 0.626806 0 1.39998 0C2.17316 0 2.79997 0.626774 2.79997 1.39998C2.79997 2.17319 2.17316 2.79997 1.39998 2.79997C0.626806 2.79997 0 2.17319 0 1.39998Z" fill="#141E12"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 857 B |
@@ -290,7 +290,7 @@ function BreadcrumbPolicyOverview() {
|
||||
policy: node(id: $policyId) {
|
||||
id
|
||||
... on Policy {
|
||||
name
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -306,7 +306,7 @@ function BreadcrumbPolicyOverview() {
|
||||
<BreadcrumbNavLink
|
||||
to={`/organizations/${organizationId}/policies/${policyId}`}
|
||||
>
|
||||
{data.policy?.name}
|
||||
{data.policy?.title}
|
||||
</BreadcrumbNavLink>
|
||||
</BreadcrumbItem>
|
||||
<Outlet />
|
||||
|
||||
@@ -19,9 +19,8 @@ import { NewPeoplePage } from "./people/NewPeoplePage";
|
||||
import { PeopleListPage } from "./people/PeopleListPage";
|
||||
import { PeoplePage } from "./people/PeoplePage";
|
||||
import { EditPolicyPage } from "./policies/EditPolicyPage";
|
||||
import { NewPolicyPage } from "./policies/NewPolicyPage";
|
||||
import { PolicyListPage } from "./policies/PolicyListPage";
|
||||
import { PolicyPage } from "./policies/PolicyPage";
|
||||
import { ShowPolicyPage } from "./policies/ShowPolicyPage";
|
||||
import { EditRiskPage } from "./risks/EditRiskPage";
|
||||
import { NewRiskPage } from "./risks/NewRiskPage";
|
||||
import { ListRiskPage } from "./risks/ListRiskPage";
|
||||
@@ -54,9 +53,8 @@ export function OrganizationsRoutes() {
|
||||
<Route path="mesures/:mesureId/edit" element={<EditMesurePage />} />
|
||||
<Route path="vendors/:vendorId" element={<VendorPage />} />
|
||||
<Route path="policies" element={<PolicyListPage />} />
|
||||
<Route path="policies/new" element={<NewPolicyPage />} />
|
||||
<Route path="policies/:policyId" element={<PolicyPage />} />
|
||||
<Route path="policies/:policyId/edit" element={<EditPolicyPage />} />
|
||||
<Route path="policies/:policyId" element={<ShowPolicyPage />} />
|
||||
<Route path="policies/:policyId/versions/:versionId/edit" element={<EditPolicyPage />} />
|
||||
<Route path="risks" element={<ListRiskPage />} />
|
||||
<Route path="risks/new" element={<NewRiskPage />} />
|
||||
<Route path="risks/:riskId" element={<ShowRiskView />} />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<5f81eaad8c66c579401d9c73180f8f91>>
|
||||
* @generated SignedSource<<ca410ce7df7231383e55d134d37916bd>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -15,7 +15,7 @@ export type OrganizationBreadcrumbBreadcrumbPolicyOverviewQuery$variables = {
|
||||
export type OrganizationBreadcrumbBreadcrumbPolicyOverviewQuery$data = {
|
||||
readonly policy: {
|
||||
readonly id: string;
|
||||
readonly name?: string;
|
||||
readonly title?: string;
|
||||
};
|
||||
};
|
||||
export type OrganizationBreadcrumbBreadcrumbPolicyOverviewQuery = {
|
||||
@@ -52,7 +52,7 @@ v3 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "name",
|
||||
"name": "title",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
@@ -112,16 +112,16 @@ return {
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "13d6ac9cf6f904f8a03f141b6b6e2564",
|
||||
"cacheID": "bc0b04bbe75c94660de230461532773a",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "OrganizationBreadcrumbBreadcrumbPolicyOverviewQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query OrganizationBreadcrumbBreadcrumbPolicyOverviewQuery(\n $policyId: ID!\n) {\n policy: node(id: $policyId) {\n __typename\n id\n ... on Policy {\n name\n }\n }\n}\n"
|
||||
"text": "query OrganizationBreadcrumbBreadcrumbPolicyOverviewQuery(\n $policyId: ID!\n) {\n policy: node(id: $policyId) {\n __typename\n id\n ... on Policy {\n title\n }\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "6ed45dd2d1fa369329edde6b77eec92d";
|
||||
(node as any).hash = "ced380d1e6eb1c31ecc5f2dc9096133b";
|
||||
|
||||
export default node;
|
||||
|
||||
@@ -106,10 +106,15 @@ const linkedPoliciesQuery = graphql`
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
content
|
||||
status
|
||||
reviewDate
|
||||
title
|
||||
description
|
||||
currentPublishedVersion
|
||||
createdAt
|
||||
updatedAt
|
||||
owner {
|
||||
id
|
||||
fullName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,10 +133,15 @@ const organizationPoliciesQuery = graphql`
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
content
|
||||
status
|
||||
reviewDate
|
||||
title
|
||||
description
|
||||
currentPublishedVersion
|
||||
createdAt
|
||||
updatedAt
|
||||
owner {
|
||||
id
|
||||
fullName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -582,7 +592,7 @@ export function Control({
|
||||
return policies.filter((policy) => {
|
||||
return (
|
||||
!policySearchQuery ||
|
||||
policy.name.toLowerCase().includes(policySearchQuery.toLowerCase()) ||
|
||||
policy.name?.toLowerCase().includes(policySearchQuery.toLowerCase()) ||
|
||||
(policy.content &&
|
||||
policy.content
|
||||
.toLowerCase()
|
||||
@@ -1066,10 +1076,7 @@ export function Control({
|
||||
<thead className="sticky top-0 bg-white">
|
||||
<tr className="border-b text-left text-sm text-secondary bg-invert-bg">
|
||||
<th className="py-3 px-4 font-medium">Name</th>
|
||||
<th className="py-3 px-4 font-medium">Status</th>
|
||||
<th className="py-3 px-4 font-medium">
|
||||
Review Date
|
||||
</th>
|
||||
<th className="py-3 px-4 font-medium">Review Date</th>
|
||||
<th className="py-3 px-4 font-medium text-right">
|
||||
Actions
|
||||
</th>
|
||||
@@ -1093,17 +1100,6 @@ export function Control({
|
||||
</div>
|
||||
)}
|
||||
</td>
|
||||
<td className="py-3 px-4">
|
||||
<div
|
||||
className={`px-2 py-0.5 rounded-full text-xs ${
|
||||
policy.status === "ACTIVE"
|
||||
? "bg-success-bg text-success"
|
||||
: "bg-secondary-bg text-secondary"
|
||||
} inline-block`}
|
||||
>
|
||||
{policy.status}
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-3 px-4">
|
||||
{policy.reviewDate
|
||||
? new Date(
|
||||
@@ -1137,7 +1133,7 @@ export function Control({
|
||||
handleLinkPolicy(policy.id)
|
||||
}
|
||||
disabled={isLinkingPolicy}
|
||||
className="text-xs h-7 text-info border-info-b hover:bg-h-info-bg"
|
||||
className="text-xs h-7 text-info border-info-b hover:bg-h-info-bg"
|
||||
>
|
||||
{isLinkingPolicy ? (
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
@@ -1193,7 +1189,6 @@ export function Control({
|
||||
<thead>
|
||||
<tr className="border-b text-left text-sm text-secondary bg-invert-bg">
|
||||
<th className="py-3 px-4 font-medium">Name</th>
|
||||
<th className="py-3 px-4 font-medium">Status</th>
|
||||
<th className="py-3 px-4 font-medium">Review Date</th>
|
||||
<th className="py-3 px-4 font-medium text-right">
|
||||
Actions
|
||||
@@ -1214,17 +1209,6 @@ export function Control({
|
||||
</div>
|
||||
)}
|
||||
</td>
|
||||
<td className="py-3 px-4">
|
||||
<div
|
||||
className={`px-2 py-0.5 rounded-full text-xs ${
|
||||
policy.status === "ACTIVE"
|
||||
? "bg-success-bg text-success"
|
||||
: "bg-secondary-bg text-secondary"
|
||||
} inline-block`}
|
||||
>
|
||||
{policy.status}
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-3 px-4">
|
||||
{policy.reviewDate
|
||||
? new Date(policy.reviewDate).toLocaleDateString()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<9bddb18bf52f14881a85995793bdc1ca>>
|
||||
* @generated SignedSource<<85bdd7f83672d1a88247d958f4b7dfaf>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -9,7 +9,6 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type PolicyStatus = "ACTIVE" | "DRAFT";
|
||||
export type ControlLinkedPoliciesQuery$variables = {
|
||||
controlId: string;
|
||||
};
|
||||
@@ -19,11 +18,16 @@ export type ControlLinkedPoliciesQuery$data = {
|
||||
readonly policies?: {
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly content: string;
|
||||
readonly createdAt: string;
|
||||
readonly currentPublishedVersion: number | null | undefined;
|
||||
readonly description: string;
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly reviewDate: string | null | undefined;
|
||||
readonly status: PolicyStatus;
|
||||
readonly owner: {
|
||||
readonly fullName: string;
|
||||
readonly id: string;
|
||||
};
|
||||
readonly title: string;
|
||||
readonly updatedAt: string;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
@@ -85,28 +89,54 @@ v4 = [
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "name",
|
||||
"name": "title",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "content",
|
||||
"name": "description",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "status",
|
||||
"name": "currentPublishedVersion",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "reviewDate",
|
||||
"name": "createdAt",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "updatedAt",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "People",
|
||||
"kind": "LinkedField",
|
||||
"name": "owner",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "fullName",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v3/*: any*/)
|
||||
@@ -244,7 +274,7 @@ return {
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "67f9bf1d5f255f5406e932abde540e08",
|
||||
"cacheID": "24c5ce380772f28348df8cf089a58a8e",
|
||||
"id": null,
|
||||
"metadata": {
|
||||
"connection": [
|
||||
@@ -261,11 +291,11 @@ return {
|
||||
},
|
||||
"name": "ControlLinkedPoliciesQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query ControlLinkedPoliciesQuery(\n $controlId: ID!\n) {\n control: node(id: $controlId) {\n __typename\n id\n ... on Control {\n policies(first: 100) {\n edges {\n node {\n id\n name\n content\n status\n reviewDate\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
|
||||
"text": "query ControlLinkedPoliciesQuery(\n $controlId: ID!\n) {\n control: node(id: $controlId) {\n __typename\n id\n ... on Control {\n policies(first: 100) {\n edges {\n node {\n id\n title\n description\n currentPublishedVersion\n createdAt\n updatedAt\n owner {\n id\n fullName\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "3904c2344754177177fa7c16129dc894";
|
||||
(node as any).hash = "d4d16cac2a05ea1d38fde55d04f21c01";
|
||||
|
||||
export default node;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<cfe85b9fdabaf24f9a7f575eca8a6417>>
|
||||
* @generated SignedSource<<1f9de20276a5c0b1291c5e25dc11990b>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -9,7 +9,6 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type PolicyStatus = "ACTIVE" | "DRAFT";
|
||||
export type ControlOrganizationPoliciesQuery$variables = {
|
||||
organizationId: string;
|
||||
};
|
||||
@@ -19,11 +18,16 @@ export type ControlOrganizationPoliciesQuery$data = {
|
||||
readonly policies?: {
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly content: string;
|
||||
readonly createdAt: string;
|
||||
readonly currentPublishedVersion: number | null | undefined;
|
||||
readonly description: string;
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly reviewDate: string | null | undefined;
|
||||
readonly status: PolicyStatus;
|
||||
readonly owner: {
|
||||
readonly fullName: string;
|
||||
readonly id: string;
|
||||
};
|
||||
readonly title: string;
|
||||
readonly updatedAt: string;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
@@ -85,28 +89,54 @@ v4 = [
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "name",
|
||||
"name": "title",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "content",
|
||||
"name": "description",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "status",
|
||||
"name": "currentPublishedVersion",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "reviewDate",
|
||||
"name": "createdAt",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "updatedAt",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "People",
|
||||
"kind": "LinkedField",
|
||||
"name": "owner",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "fullName",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v3/*: any*/)
|
||||
@@ -244,7 +274,7 @@ return {
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "3a3c91f3e18e8a4b66011aa583e262e4",
|
||||
"cacheID": "94e32c7758c8df264b99a44150de687d",
|
||||
"id": null,
|
||||
"metadata": {
|
||||
"connection": [
|
||||
@@ -261,11 +291,11 @@ return {
|
||||
},
|
||||
"name": "ControlOrganizationPoliciesQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query ControlOrganizationPoliciesQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ... on Organization {\n policies(first: 100) {\n edges {\n node {\n id\n name\n content\n status\n reviewDate\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
|
||||
"text": "query ControlOrganizationPoliciesQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ... on Organization {\n policies(first: 100) {\n edges {\n node {\n id\n title\n description\n currentPublishedVersion\n createdAt\n updatedAt\n owner {\n id\n fullName\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "f4699d729e6df6192efd201047689204";
|
||||
(node as any).hash = "47aaf55ffb65d3220df2b0fe2688bc12";
|
||||
|
||||
export default node;
|
||||
|
||||
@@ -10,27 +10,30 @@ import {
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { toast } from "@/hooks/use-toast";
|
||||
import { Calendar, User } from "lucide-react";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import { Suspense } from "react";
|
||||
import PolicyEditor from "@/components/PolicyEditor";
|
||||
import PeopleSelector from "@/components/PeopleSelector";
|
||||
import type { EditPolicyViewQuery } from "./__generated__/EditPolicyViewQuery.graphql";
|
||||
import type { EditPolicyViewMutation as EditPolicyViewMutationType } from "./__generated__/EditPolicyViewMutation.graphql";
|
||||
import { PageTemplate } from "@/components/PageTemplate";
|
||||
import { EditPolicyViewSkeleton } from "./EditPolicyPage";
|
||||
import { User } from "lucide-react";
|
||||
|
||||
const editPolicyViewQuery = graphql`
|
||||
query EditPolicyViewQuery($policyId: ID!, $organizationId: ID!) {
|
||||
query EditPolicyViewQuery($policyId: ID!, $organizationId: ID!, $policyVersionId: ID!) {
|
||||
policyVersion: node(id: $policyVersionId) {
|
||||
id
|
||||
... on PolicyVersion {
|
||||
content
|
||||
}
|
||||
}
|
||||
|
||||
policy: node(id: $policyId) {
|
||||
id
|
||||
... on Policy {
|
||||
name
|
||||
content
|
||||
status
|
||||
reviewDate
|
||||
title
|
||||
owner {
|
||||
id
|
||||
fullName
|
||||
@@ -44,18 +47,11 @@ const editPolicyViewQuery = graphql`
|
||||
`;
|
||||
|
||||
const UpdatePolicyMutation = graphql`
|
||||
mutation EditPolicyViewMutation($input: UpdatePolicyInput!) {
|
||||
updatePolicy(input: $input) {
|
||||
policy {
|
||||
mutation EditPolicyViewMutation($input: UpdatePolicyVersionInput!) {
|
||||
updatePolicyVersion(input: $input) {
|
||||
policyVersion {
|
||||
id
|
||||
name
|
||||
content
|
||||
status
|
||||
reviewDate
|
||||
owner {
|
||||
id
|
||||
fullName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,56 +63,28 @@ function EditPolicyViewContent({
|
||||
queryRef: PreloadedQuery<EditPolicyViewQuery>;
|
||||
}) {
|
||||
const navigate = useNavigate();
|
||||
const { organizationId, policyId } = useParams();
|
||||
const { organizationId, policyId, versionId } = useParams();
|
||||
const data = usePreloadedQuery<EditPolicyViewQuery>(
|
||||
editPolicyViewQuery,
|
||||
queryRef
|
||||
);
|
||||
|
||||
console.log("EditPolicyView data:", data.policy);
|
||||
const [content, setContent] = useState(data.policyVersion.content || "");
|
||||
|
||||
const [name, setName] = useState(data.policy.name);
|
||||
const [content, setContent] = useState(data.policy.content || "");
|
||||
const [status, setStatus] = useState(data.policy.status);
|
||||
const [reviewDate, setReviewDate] = useState(data.policy.reviewDate || "");
|
||||
const [ownerId, setOwnerId] = useState<string | null>(
|
||||
data.policy.owner?.id || null
|
||||
);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
console.log(
|
||||
"EditPolicyView state - content:",
|
||||
content
|
||||
? content.substring(0, 50) + (content.length > 50 ? "..." : "")
|
||||
: "empty"
|
||||
);
|
||||
|
||||
const [commitMutation] =
|
||||
const [updatePolicy, isSubmitting] =
|
||||
useMutation<EditPolicyViewMutationType>(UpdatePolicyMutation);
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setIsSubmitting(true);
|
||||
|
||||
// Convert reviewDate string to ISO format for the API
|
||||
let reviewDateValue = null;
|
||||
if (reviewDate) {
|
||||
reviewDateValue = new Date(reviewDate).toISOString();
|
||||
}
|
||||
|
||||
commitMutation({
|
||||
updatePolicy({
|
||||
variables: {
|
||||
input: {
|
||||
id: data.policy.id,
|
||||
name,
|
||||
policyVersionId: data.policyVersion.id,
|
||||
content,
|
||||
status,
|
||||
reviewDate: reviewDateValue,
|
||||
ownerId,
|
||||
},
|
||||
},
|
||||
onCompleted: (response, errors) => {
|
||||
setIsSubmitting(false);
|
||||
onCompleted: (_, errors) => {
|
||||
if (errors) {
|
||||
console.error("Error updating policy:", errors);
|
||||
toast({
|
||||
@@ -134,7 +102,6 @@ function EditPolicyViewContent({
|
||||
navigate(`/organizations/${organizationId}/policies/${policyId}`);
|
||||
},
|
||||
onError: (error) => {
|
||||
setIsSubmitting(false);
|
||||
console.error("Error updating policy:", error);
|
||||
toast({
|
||||
title: "Error",
|
||||
@@ -153,77 +120,20 @@ function EditPolicyViewContent({
|
||||
<CardTitle>Policy Information</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name">Policy Name</Label>
|
||||
<Input
|
||||
id="name"
|
||||
placeholder="Enter policy name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="content">Policy Content</Label>
|
||||
<div className="min-h-[300px]">
|
||||
<PolicyEditor
|
||||
initialContent={content}
|
||||
onChange={(html) => setContent(html)}
|
||||
<div className="min-h-[500px]">
|
||||
<Textarea
|
||||
id="content"
|
||||
placeholder="Enter policy description"
|
||||
value={content}
|
||||
onChange={(e) => setContent(e.target.value)}
|
||||
required
|
||||
rows={20}
|
||||
className="min-h-[500px] resize-y"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Status</Label>
|
||||
<RadioGroup
|
||||
value={status}
|
||||
onValueChange={(value) =>
|
||||
setStatus(value as "DRAFT" | "ACTIVE")
|
||||
}
|
||||
className="flex space-x-4"
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="DRAFT" id="draft" />
|
||||
<Label htmlFor="draft" className="cursor-pointer">
|
||||
Draft
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="ACTIVE" id="active" />
|
||||
<Label htmlFor="active" className="cursor-pointer">
|
||||
Active
|
||||
</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="owner" className="flex items-center gap-2">
|
||||
<User className="h-4 w-4" />
|
||||
Policy Owner
|
||||
</Label>
|
||||
<PeopleSelector
|
||||
organizationRef={data.organization}
|
||||
selectedPersonId={ownerId}
|
||||
onSelect={setOwnerId}
|
||||
placeholder="Select policy owner"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="reviewDate" className="flex items-center gap-2">
|
||||
<Calendar className="h-4 w-4" />
|
||||
Review Date
|
||||
</Label>
|
||||
<Input
|
||||
id="reviewDate"
|
||||
type="date"
|
||||
value={reviewDate}
|
||||
onChange={(e) => setReviewDate(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -250,15 +160,17 @@ function EditPolicyViewContent({
|
||||
}
|
||||
|
||||
export default function EditPolicyView() {
|
||||
const { organizationId, policyId } = useParams();
|
||||
const { organizationId, policyId, versionId } = useParams();
|
||||
const [queryRef, loadQuery] =
|
||||
useQueryLoader<EditPolicyViewQuery>(editPolicyViewQuery);
|
||||
|
||||
useEffect(() => {
|
||||
if (organizationId && policyId) {
|
||||
loadQuery({ organizationId, policyId });
|
||||
}
|
||||
}, [organizationId, policyId, loadQuery]);
|
||||
loadQuery({
|
||||
organizationId: organizationId!,
|
||||
policyId: policyId!,
|
||||
policyVersionId: versionId!,
|
||||
});
|
||||
}, [organizationId, policyId, versionId]);
|
||||
|
||||
if (!queryRef) {
|
||||
return <EditPolicyViewSkeleton />;
|
||||
@@ -266,7 +178,7 @@ export default function EditPolicyView() {
|
||||
|
||||
return (
|
||||
<Suspense fallback={<EditPolicyViewSkeleton />}>
|
||||
{queryRef && <EditPolicyViewContent queryRef={queryRef} />}
|
||||
<EditPolicyViewContent queryRef={queryRef}/>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import { PageTemplateSkeleton } from "@/components/PageTemplate";
|
||||
import { Suspense } from "react";
|
||||
import { useLocation } from "react-router";
|
||||
import { ErrorBoundaryWithLocation } from "../ErrorBoundary";
|
||||
import { lazy } from "@probo/react-lazy";
|
||||
|
||||
const NewPolicyView = lazy(() => import("./NewPolicyView"));
|
||||
|
||||
export function NewPolicyViewSkeleton() {
|
||||
return (
|
||||
<PageTemplateSkeleton
|
||||
title="New Policy"
|
||||
description="Add a new policy for your organization"
|
||||
>
|
||||
<div className="bg-subtle-bg animate-pulse rounded-lg h-[600px]" />
|
||||
</PageTemplateSkeleton>
|
||||
);
|
||||
}
|
||||
|
||||
export function NewPolicyPage() {
|
||||
const location = useLocation();
|
||||
|
||||
return (
|
||||
<Suspense key={location.pathname} fallback={<NewPolicyViewSkeleton />}>
|
||||
<ErrorBoundaryWithLocation>
|
||||
<NewPolicyView />
|
||||
</ErrorBoundaryWithLocation>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
@@ -1,281 +0,0 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useNavigate, useParams } from "react-router";
|
||||
import {
|
||||
ConnectionHandler,
|
||||
graphql,
|
||||
useMutation,
|
||||
useQueryLoader,
|
||||
usePreloadedQuery,
|
||||
PreloadedQuery,
|
||||
} from "react-relay";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { Calendar, User } from "lucide-react";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import PolicyEditor from "@/components/PolicyEditor";
|
||||
import PeopleSelector from "@/components/PeopleSelector";
|
||||
import { Suspense } from "react";
|
||||
import type { NewPolicyViewMutation } from "./__generated__/NewPolicyViewMutation.graphql";
|
||||
import type { NewPolicyViewQuery } from "./__generated__/NewPolicyViewQuery.graphql";
|
||||
import { PageTemplate } from "@/components/PageTemplate";
|
||||
import { NewPolicyViewSkeleton } from "./NewPolicyPage";
|
||||
|
||||
const newPolicyQuery = graphql`
|
||||
query NewPolicyViewQuery($organizationId: ID!) {
|
||||
organization: node(id: $organizationId) {
|
||||
...PeopleSelector_organization
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const CreatePolicyMutation = graphql`
|
||||
mutation NewPolicyViewMutation(
|
||||
$input: CreatePolicyInput!
|
||||
$connections: [ID!]!
|
||||
) {
|
||||
createPolicy(input: $input) {
|
||||
policyEdge @prependEdge(connections: $connections) {
|
||||
node {
|
||||
id
|
||||
name
|
||||
content
|
||||
status
|
||||
reviewDate
|
||||
owner {
|
||||
id
|
||||
fullName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
function CreatePolicyForm({
|
||||
queryRef,
|
||||
}: {
|
||||
queryRef: PreloadedQuery<NewPolicyViewQuery>;
|
||||
}) {
|
||||
const navigate = useNavigate();
|
||||
const { organizationId } = useParams();
|
||||
const data = usePreloadedQuery<NewPolicyViewQuery>(newPolicyQuery, queryRef);
|
||||
const [name, setName] = useState("");
|
||||
const [content, setContent] = useState("");
|
||||
const [status, setStatus] = useState<"DRAFT" | "ACTIVE">("DRAFT");
|
||||
const [reviewDate, setReviewDate] = useState("");
|
||||
const [ownerId, setOwnerId] = useState<string | null>(null);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const { toast } = useToast();
|
||||
|
||||
console.log(
|
||||
"NewPolicyView state - content:",
|
||||
content
|
||||
? content.substring(0, 50) + (content.length > 50 ? "..." : "")
|
||||
: "empty"
|
||||
);
|
||||
|
||||
const [createPolicy] =
|
||||
useMutation<NewPolicyViewMutation>(CreatePolicyMutation);
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!ownerId) {
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Please select an owner for the policy.",
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSubmitting(true);
|
||||
|
||||
// Convert reviewDate string to ISO format for the API
|
||||
let reviewDateValue = null;
|
||||
if (reviewDate) {
|
||||
reviewDateValue = new Date(reviewDate).toISOString();
|
||||
}
|
||||
|
||||
const input = {
|
||||
organizationId: organizationId!,
|
||||
name,
|
||||
content,
|
||||
status,
|
||||
reviewDate: reviewDateValue,
|
||||
ownerId,
|
||||
};
|
||||
|
||||
createPolicy({
|
||||
variables: {
|
||||
input,
|
||||
connections: [
|
||||
ConnectionHandler.getConnectionID(
|
||||
organizationId!,
|
||||
"PolicyListView_policies"
|
||||
),
|
||||
],
|
||||
},
|
||||
onCompleted: (response, errors) => {
|
||||
setIsSubmitting(false);
|
||||
if (errors) {
|
||||
console.error("Error creating policy:", errors);
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Failed to create policy. Please try again.",
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
toast({
|
||||
title: "Success",
|
||||
description: "Policy created successfully!",
|
||||
});
|
||||
|
||||
navigate(
|
||||
`/organizations/${organizationId}/policies/${response.createPolicy.policyEdge.node.id}`
|
||||
);
|
||||
},
|
||||
onError: (error) => {
|
||||
setIsSubmitting(false);
|
||||
console.error("Error creating policy:", error);
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Failed to create policy. Please try again.",
|
||||
variant: "destructive",
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<PageTemplate
|
||||
title="Create Policy"
|
||||
description="Create a new policy for your organization"
|
||||
>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="grid gap-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Policy Information</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name">Policy Name</Label>
|
||||
<Input
|
||||
id="name"
|
||||
placeholder="Enter policy name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="content">Policy Content</Label>
|
||||
<div className="min-h-[300px]">
|
||||
<PolicyEditor
|
||||
initialContent={content}
|
||||
onChange={(html) => setContent(html)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Status</Label>
|
||||
<RadioGroup
|
||||
value={status}
|
||||
onValueChange={(value: "DRAFT" | "ACTIVE") =>
|
||||
setStatus(value)
|
||||
}
|
||||
className="flex space-x-4"
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="DRAFT" id="draft" />
|
||||
<Label htmlFor="draft" className="cursor-pointer">
|
||||
Draft
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="ACTIVE" id="active" />
|
||||
<Label htmlFor="active" className="cursor-pointer">
|
||||
Active
|
||||
</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="owner" className="flex items-center gap-2">
|
||||
<User className="h-4 w-4" />
|
||||
Policy Owner
|
||||
</Label>
|
||||
<PeopleSelector
|
||||
organizationRef={data.organization}
|
||||
selectedPersonId={ownerId}
|
||||
onSelect={setOwnerId}
|
||||
placeholder="Select policy owner"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="reviewDate" className="flex items-center gap-2">
|
||||
<Calendar className="h-4 w-4" />
|
||||
Review Date
|
||||
</Label>
|
||||
<Input
|
||||
id="reviewDate"
|
||||
type="date"
|
||||
value={reviewDate}
|
||||
onChange={(e) => setReviewDate(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<div className="flex justify-end gap-4">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() =>
|
||||
navigate(`/organizations/${organizationId}/policies`)
|
||||
}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" disabled={isSubmitting}>
|
||||
{isSubmitting ? "Creating..." : "Create Policy"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</PageTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
export default function NewPolicyView() {
|
||||
const { organizationId } = useParams();
|
||||
const [queryRef, loadQuery] =
|
||||
useQueryLoader<NewPolicyViewQuery>(newPolicyQuery);
|
||||
|
||||
useEffect(() => {
|
||||
if (organizationId) {
|
||||
loadQuery({ organizationId });
|
||||
}
|
||||
}, [organizationId, loadQuery]);
|
||||
|
||||
if (!queryRef) {
|
||||
return <NewPolicyViewSkeleton />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Suspense fallback={<NewPolicyViewSkeleton />}>
|
||||
{<CreatePolicyForm queryRef={queryRef} />}
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
MoreHorizontal,
|
||||
Trash2,
|
||||
Eye,
|
||||
X,
|
||||
} from "lucide-react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
@@ -24,24 +25,59 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import type { PolicyListViewQuery as PolicyListViewQueryType } from "./__generated__/PolicyListViewQuery.graphql";
|
||||
import { format } from "date-fns";
|
||||
import type { PolicyListViewQuery, PolicyListViewQuery$data } from "./__generated__/PolicyListViewQuery.graphql";
|
||||
import type { PolicyListViewDeleteMutation } from "./__generated__/PolicyListViewDeleteMutation.graphql";
|
||||
import type { PolicyListViewCreateMutation } from "./__generated__/PolicyListViewCreateMutation.graphql";
|
||||
import { PageTemplate } from "@/components/PageTemplate";
|
||||
import { PolicyListViewSkeleton } from "./PolicyListPage";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Avatar } from "@/components/ui/avatar";
|
||||
import { cn } from "@/lib/utils";
|
||||
import PeopleSelector from "@/components/PeopleSelector";
|
||||
import type { PeopleSelector_organization$key } from "@/components/__generated__/PeopleSelector_organization.graphql";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
|
||||
const PolicyListViewQuery = graphql`
|
||||
const policyListViewQuery = graphql`
|
||||
query PolicyListViewQuery($organizationId: ID!) {
|
||||
viewer {
|
||||
user {
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
organization: node(id: $organizationId) {
|
||||
... on Organization {
|
||||
policies(first: 100) @connection(key: "PolicyListView_policies") {
|
||||
...PeopleSelector_organization
|
||||
policies(first: 50, orderBy: {field: TITLE, direction: ASC}) @connection(key: "PolicyListView_policies") {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
content
|
||||
title
|
||||
description
|
||||
currentPublishedVersion
|
||||
createdAt
|
||||
updatedAt
|
||||
status
|
||||
owner {
|
||||
id
|
||||
fullName
|
||||
}
|
||||
versions(first: 1) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
status
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,29 +97,47 @@ const DeletePolicyMutation = graphql`
|
||||
}
|
||||
`;
|
||||
|
||||
const createPolicyMutation = graphql`
|
||||
mutation PolicyListViewCreateMutation(
|
||||
$input: CreatePolicyInput!
|
||||
$connections: [ID!]!
|
||||
) {
|
||||
createPolicy(input: $input) {
|
||||
policyEdge @prependEdge(connections: $connections) {
|
||||
node {
|
||||
id
|
||||
title
|
||||
description
|
||||
createdAt
|
||||
updatedAt
|
||||
owner {
|
||||
id
|
||||
fullName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
function PolicyTableRow({
|
||||
policy,
|
||||
organizationId,
|
||||
}: {
|
||||
policy: {
|
||||
id: string;
|
||||
name: string;
|
||||
content?: string;
|
||||
status?: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
policy: NonNullable<PolicyListViewQuery$data["organization"]["policies"]>["edges"][0]["node"];
|
||||
organizationId: string;
|
||||
}) {
|
||||
const navigate = useNavigate();
|
||||
const { toast } = useToast();
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
const [commitDeleteMutation] = useMutation<PolicyListViewDeleteMutation>(DeletePolicyMutation);
|
||||
const [deletePolicy, isDeleting] = useMutation<PolicyListViewDeleteMutation>(DeletePolicyMutation);
|
||||
|
||||
const handleDelete = (e: React.MouseEvent) => {
|
||||
const latestVersion = policy.versions?.edges[0]?.node;
|
||||
const status = latestVersion?.status || "DRAFT";
|
||||
|
||||
const handleDeletePolicy = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
if (window.confirm("Are you sure you want to delete this policy? This action cannot be undone.")) {
|
||||
setIsDeleting(true);
|
||||
commitDeleteMutation({
|
||||
deletePolicy({
|
||||
variables: {
|
||||
input: {
|
||||
policyId: policy.id,
|
||||
@@ -96,7 +150,6 @@ function PolicyTableRow({
|
||||
],
|
||||
},
|
||||
onCompleted: (_, errors) => {
|
||||
setIsDeleting(false);
|
||||
if (errors) {
|
||||
console.error("Error deleting policy:", errors);
|
||||
toast({
|
||||
@@ -112,7 +165,6 @@ function PolicyTableRow({
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
setIsDeleting(false);
|
||||
console.error("Error deleting policy:", error);
|
||||
toast({
|
||||
title: "Error",
|
||||
@@ -131,27 +183,33 @@ function PolicyTableRow({
|
||||
|
||||
return (
|
||||
<tr
|
||||
className="border-t border-[#ECEFEC] hover:bg-[rgba(5,77,5,0.01)] cursor-pointer"
|
||||
className="border-t border-solid-b hover:bg-subtle-bg cursor-pointer"
|
||||
onClick={() => {
|
||||
navigate(`/organizations/${organizationId}/policies/${policy.id}`);
|
||||
}}
|
||||
>
|
||||
<td className="py-4 px-6">
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium text-[#141E12]">{policy.name}</span>
|
||||
<span className="text-sm text-[#818780]">
|
||||
Description
|
||||
<span className="font-medium text-primary">{policy.title}</span>
|
||||
<span className="text-sm text-tertiary">
|
||||
{policy.description || "No description provided"}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-4 px-6">
|
||||
<span className="text-sm text-[#141E12]">Mon, 8 Mar. 2025</span>
|
||||
<span className="text-sm text-primary">
|
||||
{format(new Date(policy.updatedAt), "MMM d, yyyy")}
|
||||
</span>
|
||||
</td>
|
||||
<td className="py-4 px-6">
|
||||
<Badge
|
||||
className="bg-[rgba(5,77,5,0.03)] text-[#6B716A] font-medium border-0 py-0 px-[6px] h-5 text-xs rounded-md"
|
||||
className={`font-medium border-0 py-0 px-[6px] h-5 text-xs rounded-md ${
|
||||
status === "PUBLISHED"
|
||||
? "bg-success-bg text-success"
|
||||
: "bg-secondary-bg text-tertiary"
|
||||
}`}
|
||||
>
|
||||
Draft
|
||||
{status === "PUBLISHED" ? "Published" : "Draft"}
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="py-4 px-6 text-right">
|
||||
@@ -172,8 +230,8 @@ function PolicyTableRow({
|
||||
View
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={handleDelete}
|
||||
className="text-red-600 focus:text-red-600 focus:bg-red-50"
|
||||
onClick={handleDeletePolicy}
|
||||
className="text-danger focus:text-danger focus:bg-danger-bg"
|
||||
disabled={isDeleting}
|
||||
>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
@@ -186,60 +244,264 @@ function PolicyTableRow({
|
||||
);
|
||||
}
|
||||
|
||||
function CreatePolicyModal({
|
||||
open,
|
||||
onOpenChange,
|
||||
organizationId,
|
||||
organizationRef,
|
||||
}: {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
organizationId: string;
|
||||
organizationRef: PeopleSelector_organization$key;
|
||||
}) {
|
||||
const { toast } = useToast();
|
||||
const [title, setTitle] = useState("");
|
||||
const [content, setContent] = useState("");
|
||||
const [ownerId, setOwnerId] = useState<string | null>(null);
|
||||
|
||||
const [createPolicy, isCreating] = useMutation<PolicyListViewCreateMutation>(createPolicyMutation);
|
||||
|
||||
// Reset form fields
|
||||
const resetForm = () => {
|
||||
setTitle("");
|
||||
setContent("");
|
||||
setOwnerId(null);
|
||||
};
|
||||
|
||||
const handleCreatePolicy = () => {
|
||||
if (!title.trim()) {
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Please enter a policy title.",
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ownerId) {
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Please select an owner for the policy.",
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const input = {
|
||||
organizationId,
|
||||
title,
|
||||
content,
|
||||
ownerId,
|
||||
};
|
||||
|
||||
createPolicy({
|
||||
variables: {
|
||||
input,
|
||||
connections: [
|
||||
ConnectionHandler.getConnectionID(
|
||||
organizationId,
|
||||
"PolicyListView_policies",
|
||||
{orderBy: {field: "TITLE", direction: "ASC"}}
|
||||
),
|
||||
],
|
||||
},
|
||||
onCompleted: (response, errors) => {
|
||||
if (errors) {
|
||||
console.error("Error creating policy:", errors);
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Failed to create policy. Please try again.",
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
toast({
|
||||
title: "Success",
|
||||
description: "Policy created successfully!",
|
||||
});
|
||||
|
||||
resetForm();
|
||||
onOpenChange(false);
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Error creating policy:", error);
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Failed to create policy. Please try again.",
|
||||
variant: "destructive",
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Reset form when modal closes
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
resetForm();
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-[1080px] h-[700px] max-h-[700px] p-0 gap-0 flex flex-col">
|
||||
<DialogTitle className="sr-only">Create new policy</DialogTitle>
|
||||
<DialogDescription className="sr-only">Form to create a new policy with title, content, and owner information.</DialogDescription>
|
||||
<div className="flex justify-between items-center py-2 px-4 border-b border-solid-b h-[40px]">
|
||||
<div className="flex items-center gap-1 text-sm">
|
||||
<span className="text-tertiary">Policies</span>
|
||||
<ChevronDown className="h-3 w-3 text-quaternary rotate-[270deg]" />
|
||||
<span className="font-medium">New policy</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
<div className="flex-1 p-6 overflow-y-auto">
|
||||
<div className="mb-6">
|
||||
<h1
|
||||
className={`text-4xl leading-tight font-bold outline-none focus:outline-none ${!title ? 'text-gray-400' : 'text-black'}`}
|
||||
contentEditable
|
||||
suppressContentEditableWarning
|
||||
onBlur={(e) => setTitle(e.currentTarget.textContent || "")}
|
||||
style={{ WebkitTapHighlightColor: 'transparent' }}
|
||||
onClick={(e) => {
|
||||
if (!title) {
|
||||
e.currentTarget.textContent = '';
|
||||
}
|
||||
}}
|
||||
onFocus={(e) => {
|
||||
if (!title) {
|
||||
e.currentTarget.textContent = '';
|
||||
}
|
||||
}}
|
||||
>
|
||||
{title || "Enter policy title..."}
|
||||
</h1>
|
||||
</div>
|
||||
<Textarea
|
||||
placeholder="This Privacy Policy outlines how NovaSoft collects, uses, and protects personal information provided by users of its services. By accessing or using our platform, you agree to the collection and use of information in accordance with this policy..."
|
||||
className="min-h-[300px] border-none resize-none p-0 focus-visible:ring-0 focus-visible:ring-offset-0"
|
||||
value={content}
|
||||
onChange={(e) => setContent(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-[420px] bg-[rgba(5,77,5,0.03)] p-6 flex flex-col gap-4">
|
||||
<h3 className="font-medium text-base">Properties</h3>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex justify-between items-center py-2 border-t border-[rgba(2,42,2,0.08)]">
|
||||
<span className="text-sm font-medium text-tertiary">Status</span>
|
||||
<div className="bg-[rgba(0,39,0,0.05)] py-1.5 px-2 rounded-lg">
|
||||
<span className="text-sm font-medium">Draft</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center py-2 border-t border-[rgba(2,42,2,0.08)]">
|
||||
<span className="text-sm font-medium text-tertiary">Owner</span>
|
||||
<div className="relative">
|
||||
<PeopleSelector
|
||||
organizationRef={organizationRef}
|
||||
selectedPersonId={ownerId}
|
||||
onSelect={setOwnerId}
|
||||
placeholder="Select owner"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center py-2 border-t border-[rgba(2,42,2,0.08)]">
|
||||
<span className="text-sm font-medium text-tertiary">Review date</span>
|
||||
<Button size="icon" variant="outline" className="h-8 w-8 rounded-full bg-[rgba(0,39,0,0.05)] border-none">
|
||||
<Plus className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter className="flex justify-end items-center gap-3 px-4 py-2 border-t border-solid-b h-[60px]">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => onOpenChange(false)}
|
||||
className="h-9"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleCreatePolicy}
|
||||
disabled={isCreating}
|
||||
className="h-9"
|
||||
>
|
||||
{isCreating ? "Creating..." : "Create policy"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
function PolicyListViewContent({
|
||||
queryRef,
|
||||
}: {
|
||||
queryRef: PreloadedQuery<PolicyListViewQueryType>;
|
||||
queryRef: PreloadedQuery<PolicyListViewQuery>;
|
||||
}) {
|
||||
const data = usePreloadedQuery<PolicyListViewQueryType>(
|
||||
PolicyListViewQuery,
|
||||
const data = usePreloadedQuery<PolicyListViewQuery>(
|
||||
policyListViewQuery,
|
||||
queryRef
|
||||
);
|
||||
const { organizationId } = useParams();
|
||||
const policies =
|
||||
data.organization.policies?.edges.map((edge) => edge?.node) ?? [];
|
||||
data.organization?.policies?.edges.map((edge) => edge?.node) ?? [];
|
||||
|
||||
const [createModalOpen, setCreateModalOpen] = useState(false);
|
||||
|
||||
const handleOpenModal = () => {
|
||||
setCreateModalOpen(true);
|
||||
};
|
||||
|
||||
const handleCloseModal = (open: boolean) => {
|
||||
setCreateModalOpen(open);
|
||||
};
|
||||
|
||||
return (
|
||||
<PageTemplate
|
||||
title="Policies"
|
||||
actions={
|
||||
<Button asChild>
|
||||
<Link to={`/organizations/${organizationId}/policies/new`}>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Create policy
|
||||
</Link>
|
||||
<Button onClick={handleOpenModal}>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
New policy
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
{/* Policy table */}
|
||||
<div className="rounded-lg border border-[#ECEFEC] overflow-hidden bg-white">
|
||||
<div className="rounded-lg border border-solid-b overflow-hidden bg-level-1">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="bg-white text-left">
|
||||
<th className="py-3 px-6 text-xs font-medium text-[#818780] border-b border-[rgba(2,42,2,0.08)]">
|
||||
<tr className="bg-level-1 text-left">
|
||||
<th className="py-3 px-6 text-xs font-medium text-tertiary border-b border-low-b">
|
||||
<div className="flex items-center gap-1">
|
||||
Vendor
|
||||
<ChevronDown className="h-3 w-3 text-[#C3C8C2]" />
|
||||
Policy
|
||||
<ChevronDown className="h-3 w-3 text-quaternary" />
|
||||
</div>
|
||||
</th>
|
||||
<th className="py-3 px-6 text-xs font-medium text-[#818780] border-b border-[rgba(2,42,2,0.08)]">
|
||||
<th className="py-3 px-6 text-xs font-medium text-tertiary border-b border-low-b">
|
||||
<div className="flex items-center gap-1">
|
||||
Last update
|
||||
<ChevronDown className="h-3 w-3 text-[#C3C8C2]" />
|
||||
<ChevronDown className="h-3 w-3 text-quaternary" />
|
||||
</div>
|
||||
</th>
|
||||
<th className="py-3 px-6 text-xs font-medium text-[#818780] border-b border-[rgba(2,42,2,0.08)]">
|
||||
<th className="py-3 px-6 text-xs font-medium text-tertiary border-b border-low-b">
|
||||
<div className="flex items-center gap-1">
|
||||
Status
|
||||
<ChevronDown className="h-3 w-3 text-[#C3C8C2]" />
|
||||
<ChevronDown className="h-3 w-3 text-quaternary" />
|
||||
</div>
|
||||
</th>
|
||||
<th className="py-3 px-6 text-right text-xs font-medium text-[#818780] border-b border-[rgba(2,42,2,0.08)]"></th>
|
||||
<th className="py-3 px-6 text-right text-xs font-medium text-tertiary border-b border-low-b"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white">
|
||||
<tbody className="bg-level-1">
|
||||
{policies.length > 0 ? (
|
||||
policies.map((policy) => (
|
||||
policies.map((policy: any) => (
|
||||
<PolicyTableRow
|
||||
key={policy.id}
|
||||
policy={policy}
|
||||
@@ -251,14 +513,12 @@ function PolicyListViewContent({
|
||||
<td colSpan={4} className="py-12 text-center">
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<h3 className="text-lg font-medium">No policies found</h3>
|
||||
<p className="text-[#818780]">
|
||||
<p className="text-tertiary">
|
||||
Create your first policy to get started
|
||||
</p>
|
||||
<Button asChild>
|
||||
<Link to={`/organizations/${organizationId}/policies/new`}>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Create policy
|
||||
</Link>
|
||||
<Button onClick={handleOpenModal}>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Create policy
|
||||
</Button>
|
||||
</div>
|
||||
</td>
|
||||
@@ -267,13 +527,20 @@ function PolicyListViewContent({
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<CreatePolicyModal
|
||||
open={createModalOpen}
|
||||
onOpenChange={handleCloseModal}
|
||||
organizationId={organizationId!}
|
||||
organizationRef={data.organization}
|
||||
/>
|
||||
</PageTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
export default function PolicyListView() {
|
||||
const [queryRef, loadQuery] =
|
||||
useQueryLoader<PolicyListViewQueryType>(PolicyListViewQuery);
|
||||
useQueryLoader<PolicyListViewQuery>(policyListViewQuery);
|
||||
|
||||
const { organizationId } = useParams();
|
||||
|
||||
|
||||
@@ -1,423 +0,0 @@
|
||||
import { Suspense, useEffect, useState, useCallback } from "react";
|
||||
import { useParams, Link, useNavigate } from "react-router";
|
||||
import {
|
||||
graphql,
|
||||
PreloadedQuery,
|
||||
usePreloadedQuery,
|
||||
useQueryLoader,
|
||||
useMutation,
|
||||
ConnectionHandler,
|
||||
} from "react-relay";
|
||||
import { Edit, Download, Shield, User, FileText, Calendar } from "lucide-react";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import type { PolicyViewQuery as PolicyViewQueryType } from "./__generated__/PolicyViewQuery.graphql";
|
||||
import type { PolicyViewDeleteMutation } from "./__generated__/PolicyViewDeleteMutation.graphql";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { PageTemplate } from "@/components/PageTemplate";
|
||||
import { PolicyViewSkeleton } from "./PolicyPage";
|
||||
|
||||
const PolicyViewQuery = graphql`
|
||||
query PolicyViewQuery($policyId: ID!) {
|
||||
node(id: $policyId) {
|
||||
id
|
||||
... on Policy {
|
||||
name
|
||||
content
|
||||
createdAt
|
||||
updatedAt
|
||||
reviewDate
|
||||
status
|
||||
owner {
|
||||
id
|
||||
fullName
|
||||
primaryEmailAddress
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const DeletePolicyMutation = graphql`
|
||||
mutation PolicyViewDeleteMutation(
|
||||
$input: DeletePolicyInput!
|
||||
$connections: [ID!]!
|
||||
) {
|
||||
deletePolicy(input: $input) {
|
||||
deletedPolicyId @deleteEdge(connections: $connections)
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
function PolicyViewContent({
|
||||
queryRef,
|
||||
}: {
|
||||
queryRef: PreloadedQuery<PolicyViewQueryType>;
|
||||
}) {
|
||||
const data = usePreloadedQuery(PolicyViewQuery, queryRef);
|
||||
const policy = data.node;
|
||||
const { organizationId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const [activeTab, setActiveTab] = useState("content");
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
const { toast } = useToast();
|
||||
|
||||
const [commitDeleteMutation] =
|
||||
useMutation<PolicyViewDeleteMutation>(DeletePolicyMutation);
|
||||
|
||||
const handleDeletePolicy = useCallback(() => {
|
||||
if (
|
||||
window.confirm(
|
||||
"Are you sure you want to delete this policy? This action cannot be undone."
|
||||
)
|
||||
) {
|
||||
setIsDeleting(true);
|
||||
|
||||
commitDeleteMutation({
|
||||
variables: {
|
||||
input: {
|
||||
policyId: policy.id,
|
||||
},
|
||||
connections: [
|
||||
ConnectionHandler.getConnectionID(
|
||||
organizationId!,
|
||||
"PolicyListPage_policies"
|
||||
),
|
||||
],
|
||||
},
|
||||
onCompleted: (_, errors) => {
|
||||
setIsDeleting(false);
|
||||
if (errors) {
|
||||
console.error("Error deleting policy:", errors);
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Failed to delete policy. Please try again.",
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
toast({
|
||||
title: "Success",
|
||||
description: "Policy deleted successfully.",
|
||||
});
|
||||
|
||||
navigate(`/organizations/${organizationId}/policies`);
|
||||
},
|
||||
onError: (error) => {
|
||||
setIsDeleting(false);
|
||||
console.error("Error deleting policy:", error);
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Failed to delete policy. Please try again.",
|
||||
variant: "destructive",
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [policy.id, organizationId, commitDeleteMutation, navigate]);
|
||||
|
||||
// Extract a short description from the content
|
||||
const getDescription = (content: string | undefined) => {
|
||||
if (!content) return "No description available";
|
||||
|
||||
// Remove HTML tags
|
||||
const withoutTags = content.replace(/<[^>]*>/g, "");
|
||||
// Decode HTML entities
|
||||
const decoded = withoutTags
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, "'")
|
||||
.replace(/ /g, " ");
|
||||
|
||||
// Get first paragraph or first 150 characters
|
||||
const firstParagraph = decoded.split("\n\n")[0].trim();
|
||||
return firstParagraph.length > 150
|
||||
? firstParagraph.substring(0, 150) + "..."
|
||||
: firstParagraph;
|
||||
};
|
||||
|
||||
const formatDate = (dateString: string | undefined) => {
|
||||
if (!dateString) return "N/A";
|
||||
const date = new Date(dateString);
|
||||
return date.toISOString().split("T")[0]; // YYYY-MM-DD format
|
||||
};
|
||||
|
||||
return (
|
||||
<PageTemplate
|
||||
title={policy.name ?? ""}
|
||||
description={
|
||||
<div className="flex items-center gap-2 text-tertiary">
|
||||
<Badge variant={policy.status === "ACTIVE" ? "default" : "outline"}>
|
||||
{policy.status === "ACTIVE" ? "Active" : "Draft"}
|
||||
</Badge>
|
||||
{policy.owner && (
|
||||
<div className="flex items-center gap-1">
|
||||
<User className="h-3 w-3" />
|
||||
<Link
|
||||
to={`/organizations/${organizationId}/people/${policy.owner.id}`}
|
||||
className="hover:underline"
|
||||
>
|
||||
{policy.owner.fullName}
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
{policy.reviewDate && (
|
||||
<div className="flex items-center gap-1">
|
||||
<Calendar className="h-3 w-3" />
|
||||
<span>Review: {formatDate(policy.reviewDate)}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
actions={
|
||||
<div className="flex gap-2">
|
||||
<Button variant="secondary" asChild>
|
||||
<Link
|
||||
to={`/organizations/${organizationId}/policies/${policy.id}/edit`}
|
||||
>
|
||||
<Edit className="h-4 w-4 mr-2" />
|
||||
Edit
|
||||
</Link>
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
// Logic to download policy content as PDF or text
|
||||
const element = document.createElement("a");
|
||||
const file = new Blob([policy.content || ""], {
|
||||
type: "text/plain",
|
||||
});
|
||||
element.href = URL.createObjectURL(file);
|
||||
element.download = `${policy.name}.txt`;
|
||||
document.body.appendChild(element);
|
||||
element.click();
|
||||
document.body.removeChild(element);
|
||||
}}
|
||||
>
|
||||
<Download className="h-4 w-4 mr-2" />
|
||||
Download
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<div className="lg:col-span-2">
|
||||
<Card className="border shadow-sm">
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="p-2 rounded-md bg-slate-100">
|
||||
<Shield className="h-6 w-6" />
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
{policy.status && (
|
||||
<Badge
|
||||
className={`px-3 py-1 rounded-md font-medium ${
|
||||
policy.status === "ACTIVE"
|
||||
? "bg-success-bg text-success hover:bg-h-success-bg"
|
||||
: policy.status === "DRAFT"
|
||||
? "bg-warning-bg text-warning hover:bg-h-warning-bg"
|
||||
: "bg-secondary-bg text-secondary hover:bg-h-secondary-bg"
|
||||
}`}
|
||||
>
|
||||
{policy.status === "ACTIVE"
|
||||
? "Active"
|
||||
: policy.status === "DRAFT"
|
||||
? "Draft"
|
||||
: policy.status}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1 className="text-3xl font-bold mb-3">{policy.name}</h1>
|
||||
|
||||
<p className="text-tertiary mb-6">
|
||||
{getDescription(policy.content)}
|
||||
</p>
|
||||
|
||||
<Tabs
|
||||
defaultValue="content"
|
||||
value={activeTab}
|
||||
onValueChange={setActiveTab}
|
||||
className="mb-6"
|
||||
>
|
||||
<TabsList className="border-b w-full rounded-none p-0 h-auto">
|
||||
<TabsTrigger
|
||||
value="content"
|
||||
className={`px-4 py-2 font-medium ${
|
||||
activeTab === "content"
|
||||
? "border-primary text-primary"
|
||||
: "text-tertiary"
|
||||
}`}
|
||||
>
|
||||
Policy Content
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="history"
|
||||
className={`px-4 py-2 font-medium ${
|
||||
activeTab === "history"
|
||||
? "border-primary text-primary"
|
||||
: "text-tertiary"
|
||||
}`}
|
||||
>
|
||||
Version History
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="approvals"
|
||||
className={`px-4 py-2 font-medium ${
|
||||
activeTab === "approvals"
|
||||
? "border-primary text-primary"
|
||||
: "text-tertiary"
|
||||
}`}
|
||||
>
|
||||
Approvals
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="content" className="pt-6">
|
||||
<div className="prose prose-sm md:prose-base lg:prose-lg max-w-none">
|
||||
<div
|
||||
className="policy-content"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: policy.content || "",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="history" className="pt-6">
|
||||
<div className="text-center py-12">
|
||||
<p className="text-tertiary">
|
||||
Version history will be available soon.
|
||||
</p>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="approvals" className="pt-6">
|
||||
<div className="text-center py-12">
|
||||
<p className="text-tertiary">
|
||||
Approval workflow will be available soon.
|
||||
</p>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div className="lg:col-span-1">
|
||||
<Card className="border shadow-sm mb-6">
|
||||
<CardContent className="p-6">
|
||||
<h2 className="text-xl font-semibold mb-6">Policy Details</h2>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<div className="flex items-center gap-2 text-tertiary mb-1">
|
||||
<FileText className="h-4 w-4" />
|
||||
<span className="text-sm">Last Updated</span>
|
||||
</div>
|
||||
<p className="font-medium">{formatDate(policy.updatedAt)}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="flex items-center gap-2 text-tertiary mb-1">
|
||||
<FileText className="h-4 w-4" />
|
||||
<span className="text-sm">Review Due</span>
|
||||
</div>
|
||||
<p className="font-medium">
|
||||
{policy.reviewDate
|
||||
? formatDate(policy.reviewDate)
|
||||
: "Not set"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="flex items-center gap-2 text-tertiary mb-1">
|
||||
<User className="h-4 w-4" />
|
||||
<span className="text-sm">Owner</span>
|
||||
</div>
|
||||
<Link
|
||||
to={`/organizations/${organizationId}/people/${policy.owner?.id}`}
|
||||
className="hover:underline"
|
||||
>
|
||||
<p className="font-medium">
|
||||
{policy.owner ? policy.owner.fullName : "Not assigned"}
|
||||
</p>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Button asChild className="w-full">
|
||||
<Link
|
||||
to={`/organizations/${organizationId}/policies/${policy.id}/edit`}
|
||||
>
|
||||
<Edit className="mr-2 h-4 w-4" />
|
||||
Edit Policy
|
||||
</Link>
|
||||
</Button>
|
||||
|
||||
<Card className="border shadow-sm mt-6 bg-danger-bg">
|
||||
<CardContent className="p-6">
|
||||
<h3 className="text-danger font-semibold mb-3">Danger Zone</h3>
|
||||
<p className="text-sm text-tertiary mb-4">
|
||||
Permanently delete this policy and all of its data. This action
|
||||
cannot be undone.
|
||||
</p>
|
||||
<Button
|
||||
variant="destructive"
|
||||
className="w-full"
|
||||
onClick={handleDeletePolicy}
|
||||
disabled={isDeleting}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="mr-2"
|
||||
>
|
||||
<path d="M3 6h18"></path>
|
||||
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"></path>
|
||||
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path>
|
||||
</svg>
|
||||
{isDeleting ? "Deleting..." : "Delete Policy"}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</PageTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
export default function PolicyView() {
|
||||
const [queryRef, loadQuery] =
|
||||
useQueryLoader<PolicyViewQueryType>(PolicyViewQuery);
|
||||
|
||||
const { policyId } = useParams();
|
||||
|
||||
useEffect(() => {
|
||||
loadQuery({ policyId: policyId! });
|
||||
}, [loadQuery, policyId]);
|
||||
|
||||
if (!queryRef) {
|
||||
return <PolicyViewSkeleton />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Suspense fallback={<PolicyViewSkeleton />}>
|
||||
{queryRef && <PolicyViewContent queryRef={queryRef} />}
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
@@ -5,9 +5,9 @@ import { useLocation } from "react-router";
|
||||
import { ErrorBoundaryWithLocation } from "../ErrorBoundary";
|
||||
import { lazy } from "@probo/react-lazy";
|
||||
|
||||
const PolicyView = lazy(() => import("./PolicyView"));
|
||||
const ShowPolicyView = lazy(() => import("./ShowPolicyView"));
|
||||
|
||||
export function PolicyViewSkeleton() {
|
||||
export function ShowPolicyViewSkeleton() {
|
||||
return (
|
||||
<PageTemplateSkeleton
|
||||
withDescription
|
||||
@@ -108,13 +108,13 @@ export function PolicyViewSkeleton() {
|
||||
);
|
||||
}
|
||||
|
||||
export function PolicyPage() {
|
||||
export function ShowPolicyPage() {
|
||||
const location = useLocation();
|
||||
|
||||
return (
|
||||
<Suspense key={location.pathname} fallback={<PolicyViewSkeleton />}>
|
||||
<Suspense key={location.pathname} fallback={<ShowPolicyViewSkeleton />}>
|
||||
<ErrorBoundaryWithLocation>
|
||||
<PolicyView />
|
||||
<ShowPolicyView />
|
||||
</ErrorBoundaryWithLocation>
|
||||
</Suspense>
|
||||
);
|
||||
426
apps/console/src/pages/organizations/policies/ShowPolicyView.tsx
Normal file
426
apps/console/src/pages/organizations/policies/ShowPolicyView.tsx
Normal file
@@ -0,0 +1,426 @@
|
||||
import { Suspense, useEffect, useState, useCallback, ReactNode } from "react";
|
||||
import { useParams, Link, useNavigate } from "react-router";
|
||||
import {
|
||||
graphql,
|
||||
PreloadedQuery,
|
||||
usePreloadedQuery,
|
||||
useQueryLoader,
|
||||
useMutation,
|
||||
} from "react-relay";
|
||||
import { Clock, Download, Edit, Trash2, MoreHorizontal, X, FileSignature } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { PageTemplate } from "@/components/PageTemplate";
|
||||
import type { ShowPolicyViewQuery } from "./__generated__/ShowPolicyViewQuery.graphql";
|
||||
import { ShowPolicyViewPublishMutation } from "./__generated__/ShowPolicyViewPublishMutation.graphql";
|
||||
import { ShowPolicyViewCreateDraftMutation } from "./__generated__/ShowPolicyViewCreateDraftMutation.graphql";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import { ShowPolicyViewSkeleton } from "./ShowPolicyPage";
|
||||
import { format } from "date-fns";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { SignaturesModal } from "./SignaturesModal";
|
||||
import { VersionHistoryModal } from "./VersionHistoryModal";
|
||||
|
||||
const policyViewQuery = graphql`
|
||||
query ShowPolicyViewQuery($policyId: ID!) {
|
||||
node(id: $policyId) {
|
||||
id
|
||||
... on Policy {
|
||||
title
|
||||
description
|
||||
createdAt
|
||||
updatedAt
|
||||
currentPublishedVersion
|
||||
owner {
|
||||
id
|
||||
fullName
|
||||
primaryEmailAddress
|
||||
}
|
||||
|
||||
...SignaturesModal_policyVersions
|
||||
...VersionHistoryModal_policyVersions
|
||||
|
||||
latestVersion: versions(first: 1) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
version
|
||||
status
|
||||
content
|
||||
changelog
|
||||
publishedAt
|
||||
publishedBy {
|
||||
fullName
|
||||
}
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const publishPolicyVersionMutation = graphql`
|
||||
mutation ShowPolicyViewPublishMutation($input: PublishPolicyVersionInput!) {
|
||||
publishPolicyVersion(input: $input) {
|
||||
policy {
|
||||
id
|
||||
currentPublishedVersion
|
||||
}
|
||||
policyVersion {
|
||||
id
|
||||
status
|
||||
publishedAt
|
||||
publishedBy {
|
||||
fullName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const createDraftPolicyVersionMutation = graphql`
|
||||
mutation ShowPolicyViewCreateDraftMutation($input: CreateDraftPolicyVersionInput!) {
|
||||
createDraftPolicyVersion(input: $input) {
|
||||
policyVersionEdge {
|
||||
node {
|
||||
id
|
||||
version
|
||||
status
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
function ShowPolicyContent({
|
||||
queryRef,
|
||||
}: {
|
||||
queryRef: PreloadedQuery<ShowPolicyViewQuery>;
|
||||
}) {
|
||||
const data = usePreloadedQuery<ShowPolicyViewQuery>(policyViewQuery, queryRef);
|
||||
const policy = data.node;
|
||||
const { organizationId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const { toast } = useToast();
|
||||
const [queryRef2, loadQuery] = useQueryLoader<ShowPolicyViewQuery>(policyViewQuery);
|
||||
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
const [isVersionHistoryOpen, setIsVersionHistoryOpen] = useState(false);
|
||||
const [isSignaturesModalOpen, setIsSignaturesModalOpen] = useState(false);
|
||||
|
||||
const [publishDraft, isPublishInFlight] = useMutation<ShowPolicyViewPublishMutation>(publishPolicyVersionMutation);
|
||||
const [createDraft, isCreateDraftInFlight] = useMutation<ShowPolicyViewCreateDraftMutation>(createDraftPolicyVersionMutation);
|
||||
|
||||
const latestVersionEdge = policy.latestVersion?.edges[0];
|
||||
const latestVersionNode = latestVersionEdge?.node;
|
||||
|
||||
const isDraft = latestVersionNode?.status === "DRAFT";
|
||||
|
||||
useEffect(() => {
|
||||
// No need to update selectedVersion state since we're using the VersionHistoryModal component
|
||||
}, []);
|
||||
|
||||
|
||||
// Handle delete policy
|
||||
const handleDeletePolicy = useCallback(() => {
|
||||
setIsDeleteDialogOpen(true);
|
||||
}, []);
|
||||
|
||||
// Confirm delete policy
|
||||
const confirmDeletePolicy = useCallback(() => {
|
||||
setIsDeleting(true);
|
||||
|
||||
setTimeout(() => {
|
||||
toast({
|
||||
title: "Policy deleted",
|
||||
description: "The policy has been deleted successfully",
|
||||
});
|
||||
setIsDeleting(false);
|
||||
setIsDeleteDialogOpen(false);
|
||||
navigate(`/organizations/${organizationId}/policies`);
|
||||
}, 1000);
|
||||
}, [toast, navigate, organizationId]);
|
||||
|
||||
// Navigate to publish flow
|
||||
const handlePublish = useCallback(() => {
|
||||
if (!policy.id) return;
|
||||
|
||||
publishDraft({
|
||||
variables: {
|
||||
input: {
|
||||
policyId: policy.id
|
||||
}
|
||||
},
|
||||
onCompleted: (_, errors) => {
|
||||
if (errors) {
|
||||
toast({
|
||||
title: "Error publishing policy",
|
||||
description: errors[0]?.message || "An unknown error occurred",
|
||||
variant: "destructive"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
toast({
|
||||
title: "Policy published",
|
||||
description: `The policy has been published successfully`,
|
||||
});
|
||||
|
||||
// Reload the query to refresh the data
|
||||
loadQuery({ policyId: policy.id });
|
||||
},
|
||||
onError: (error) => {
|
||||
toast({
|
||||
title: "Error publishing policy",
|
||||
description: error.message || "An unknown error occurred",
|
||||
variant: "destructive"
|
||||
});
|
||||
}
|
||||
});
|
||||
}, [policy.id, publishDraft, toast, loadQuery]);
|
||||
|
||||
// Open version history modal
|
||||
const handleVersionHistoryClick = useCallback(() => {
|
||||
setIsVersionHistoryOpen(true);
|
||||
}, []);
|
||||
|
||||
// Restore version
|
||||
const handleRestoreVersion = useCallback((versionNumber: number) => {
|
||||
// Here you would implement the logic to restore a version
|
||||
toast({
|
||||
title: "Version restored",
|
||||
description: `Version ${versionNumber} has been restored`,
|
||||
});
|
||||
setIsVersionHistoryOpen(false);
|
||||
// Reload the query to refresh the data
|
||||
if (policy.id) {
|
||||
loadQuery({ policyId: policy.id });
|
||||
}
|
||||
}, [policy.id, loadQuery, toast]);
|
||||
|
||||
// Handle edit policy
|
||||
const handleEditPolicy = useCallback(() => {
|
||||
if (!policy.id) return;
|
||||
|
||||
if (latestVersionNode?.status === "PUBLISHED") {
|
||||
// Create a new draft version first
|
||||
createDraft({
|
||||
variables: {
|
||||
input: {
|
||||
policyID: policy.id
|
||||
}
|
||||
},
|
||||
onCompleted: (response, errors) => {
|
||||
if (errors) {
|
||||
toast({
|
||||
title: "Error creating draft",
|
||||
description: errors[0]?.message || "An unknown error occurred",
|
||||
variant: "destructive"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const newDraftId = response.createDraftPolicyVersion.policyVersionEdge.node.id;
|
||||
navigate(`/organizations/${organizationId}/policies/${policy.id}/versions/${newDraftId}/edit`);
|
||||
},
|
||||
onError: (error) => {
|
||||
toast({
|
||||
title: "Error creating draft",
|
||||
description: error.message || "An unknown error occurred",
|
||||
variant: "destructive"
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Navigate directly to edit if it's already a draft
|
||||
navigate(`/organizations/${organizationId}/policies/${policy.id}/versions/${latestVersionNode?.id}/edit`);
|
||||
}
|
||||
}, [policy.id, latestVersionNode, createDraft, navigate, organizationId, toast]);
|
||||
|
||||
// Format date helper
|
||||
const formatDate = (dateString?: string) => {
|
||||
if (!dateString) return "N/A";
|
||||
const date = new Date(dateString);
|
||||
return format(date, "MMM d, yyyy");
|
||||
};
|
||||
|
||||
return (
|
||||
<PageTemplate
|
||||
title={policy.title!}
|
||||
actions={
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="rounded-full h-9 px-3 gap-1.5 shadow-sm border-[#022A0214] bg-white text-[#141E12]"
|
||||
onClick={handleVersionHistoryClick}
|
||||
>
|
||||
<Clock className="h-4 w-4" />
|
||||
<span className="font-medium">Version history</span>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="rounded-full h-9 px-3 gap-1.5 shadow-sm border-[#022A0214] bg-white text-[#141E12]"
|
||||
onClick={() => setIsSignaturesModalOpen(true)}
|
||||
>
|
||||
<FileSignature className="h-4 w-4" />
|
||||
<span className="font-medium">Signature history</span>
|
||||
</Button>
|
||||
|
||||
{isDraft && (
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
className="rounded-full h-9 px-3 gap-1.5 shadow-sm"
|
||||
onClick={handlePublish}
|
||||
disabled={isPublishInFlight}
|
||||
>
|
||||
<span className="font-medium">Publish version</span>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="rounded-full h-9 w-9 shadow-sm border-[#022A0214] bg-white text-[#141E12]"
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem asChild>
|
||||
<div onClick={handleEditPolicy}>
|
||||
<Edit className="mr-2 h-4 w-4" />
|
||||
{latestVersionNode?.status === "PUBLISHED" ? "Create new draft" : "Edit draft policy"}
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={handleDeletePolicy}
|
||||
className="text-danger focus:text-danger focus:bg-danger-bg"
|
||||
>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
Delete policy
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
{latestVersionNode ? (
|
||||
<div className="bg-white rounded-lg border border-solid-b shadow-sm p-6">
|
||||
{latestVersionNode.changelog && (
|
||||
<div className="mb-6 p-4 bg-level-1 rounded-md border border-solid-b">
|
||||
<div className="text-xs text-tertiary uppercase font-medium mb-1">Change summary</div>
|
||||
<div className="text-sm text-secondary">
|
||||
{latestVersionNode.changelog}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="prose prose-olive max-w-none">
|
||||
<ReactMarkdown>
|
||||
{latestVersionNode.content || "No content available"}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 pt-4 border-t border-solid-b text-xs text-tertiary flex justify-between items-center">
|
||||
<div>
|
||||
{latestVersionNode.status === "PUBLISHED"
|
||||
? `Published on ${formatDate(latestVersionNode.publishedAt || "")}${latestVersionNode.publishedBy ? ` by ${latestVersionNode.publishedBy.fullName}` : ''}`
|
||||
: `Last modified on ${formatDate(latestVersionNode.updatedAt)} by ${policy.owner?.fullName || 'Unknown'}`}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-white rounded-lg border border-solid-b shadow-sm p-6 text-center text-tertiary">
|
||||
No content available
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Signatures Modal */}
|
||||
<SignaturesModal
|
||||
isOpen={isSignaturesModalOpen}
|
||||
onClose={() => setIsSignaturesModalOpen(false)}
|
||||
policyRef={policy}
|
||||
owner={policy.owner}
|
||||
/>
|
||||
|
||||
{/* Version History Modal */}
|
||||
<VersionHistoryModal
|
||||
isOpen={isVersionHistoryOpen}
|
||||
onClose={() => setIsVersionHistoryOpen(false)}
|
||||
policyRef={policy}
|
||||
onRestoreVersion={handleRestoreVersion}
|
||||
/>
|
||||
|
||||
{/* Delete Confirmation Dialog */}
|
||||
<Dialog open={isDeleteDialogOpen} onOpenChange={setIsDeleteDialogOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Delete Policy</DialogTitle>
|
||||
<DialogDescription>
|
||||
Are you sure you want to delete the policy "{policy.title}"? This action cannot be undone.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setIsDeleteDialogOpen(false)}
|
||||
disabled={isDeleting}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={confirmDeletePolicy}
|
||||
disabled={isDeleting}
|
||||
>
|
||||
{isDeleting ? "Deleting..." : "Delete"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</PageTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ShowPolicyView() {
|
||||
const [queryRef, loadQuery] = useQueryLoader<ShowPolicyViewQuery>(policyViewQuery);
|
||||
const { policyId } = useParams();
|
||||
|
||||
useEffect(() => {
|
||||
loadQuery({ policyId: policyId! });
|
||||
}, [loadQuery, policyId]);
|
||||
|
||||
if (!queryRef) {
|
||||
return <ShowPolicyViewSkeleton />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Suspense fallback={<ShowPolicyViewSkeleton />}>
|
||||
<ShowPolicyContent queryRef={queryRef} />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { format } from "date-fns";
|
||||
|
||||
interface PolicyVersionSignature {
|
||||
id: string;
|
||||
state: "REQUESTED" | "SIGNED";
|
||||
signedBy: {
|
||||
fullName: string;
|
||||
};
|
||||
signedAt?: string;
|
||||
requestedAt: string;
|
||||
requestedBy: {
|
||||
fullName: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface SignaturesListProps {
|
||||
signatures: PolicyVersionSignature[];
|
||||
}
|
||||
|
||||
export function SignaturesList({ signatures }: SignaturesListProps) {
|
||||
const formatDateTime = (dateString: string) => {
|
||||
const date = new Date(dateString);
|
||||
return format(date, "h:mm a • MMM d, yyyy");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{signatures.map((signature) => (
|
||||
<div key={signature.id} className="bg-white rounded-lg border border-solid-b shadow-sm p-6">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<Avatar className="h-10 w-10">
|
||||
<AvatarImage src="" alt={signature.signedBy.fullName} />
|
||||
<AvatarFallback>
|
||||
{signature.signedBy.fullName.charAt(0)}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div>
|
||||
<div className="font-medium">{signature.signedBy.fullName}</div>
|
||||
<div className="text-sm text-tertiary">
|
||||
{signature.state === "SIGNED" && signature.signedAt
|
||||
? formatDateTime(signature.signedAt)
|
||||
: formatDateTime(signature.requestedAt)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm">
|
||||
<span className="text-muted-foreground">Status:</span>{" "}
|
||||
<span className={`px-2 py-0.5 rounded-full ${
|
||||
signature.state === "SIGNED"
|
||||
? "bg-green-100 text-green-800"
|
||||
: "bg-yellow-100 text-yellow-800"
|
||||
}`}>
|
||||
{signature.state}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
<span className="text-muted-foreground">Requested by:</span>{" "}
|
||||
{signature.requestedBy.fullName}
|
||||
</div>
|
||||
{signature.state === "SIGNED" && signature.signedAt && (
|
||||
<div className="text-sm">
|
||||
<span className="text-muted-foreground">Signed at:</span>{" "}
|
||||
{formatDateTime(signature.signedAt)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,469 @@
|
||||
import { Dialog, DialogContent } from "@/components/ui/dialog";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { format } from "date-fns";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useState, useEffect } from "react";
|
||||
import { graphql, useFragment, useMutation, useQueryLoader, usePreloadedQuery, ConnectionHandler } from "react-relay";
|
||||
import type { SignaturesModal_policyVersions$data, SignaturesModal_policyVersions$key } from "./__generated__/SignaturesModal_policyVersions.graphql";
|
||||
import type { SignaturesModalRequestSignatureMutation } from "./__generated__/SignaturesModalRequestSignatureMutation.graphql";
|
||||
import type { SignaturesModalOrganizationQuery } from "./__generated__/SignaturesModalOrganizationQuery.graphql";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { useParams } from "react-router";
|
||||
import { Loader2, CheckCircle2, Clock } from "lucide-react";
|
||||
import { PreloadedQuery } from "react-relay";
|
||||
|
||||
export const policyVersionsFragment = graphql`
|
||||
fragment SignaturesModal_policyVersions on Policy {
|
||||
title
|
||||
policyVersions: versions(first: 10) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
version
|
||||
status
|
||||
publishedAt
|
||||
updatedAt
|
||||
publishedBy {
|
||||
fullName
|
||||
}
|
||||
signatures(first: 100) @connection(key: "SignaturesModal_policyVersions_signatures") {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
state
|
||||
signedAt
|
||||
requestedAt
|
||||
signedBy {
|
||||
fullName
|
||||
id
|
||||
}
|
||||
requestedBy {
|
||||
fullName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const requestSignatureMutation = graphql`
|
||||
mutation SignaturesModalRequestSignatureMutation($input: RequestSignatureInput!, $connections: [ID!]!) {
|
||||
requestSignature(input: $input) {
|
||||
policyVersionSignatureEdge @prependEdge(connections: $connections) {
|
||||
node {
|
||||
id
|
||||
state
|
||||
signedAt
|
||||
requestedAt
|
||||
signedBy {
|
||||
fullName
|
||||
id
|
||||
}
|
||||
requestedBy {
|
||||
fullName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const organizationQuery = graphql`
|
||||
query SignaturesModalOrganizationQuery($organizationId: ID!) {
|
||||
organization: node(id: $organizationId) {
|
||||
... on Organization {
|
||||
id
|
||||
peoples(first: 100, orderBy: { direction: ASC, field: FULL_NAME }) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
fullName
|
||||
primaryEmailAddress
|
||||
kind
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface SignaturesModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
policyRef: SignaturesModal_policyVersions$key;
|
||||
owner?: {
|
||||
fullName: string;
|
||||
} | null;
|
||||
}
|
||||
|
||||
// Define the signature type based on the GraphQL response
|
||||
// interface Signature {
|
||||
// id: string;
|
||||
// state: string;
|
||||
// signedAt?: string | null;
|
||||
// requestedAt: string;
|
||||
// signedBy: {
|
||||
// fullName: string;
|
||||
// id: string;
|
||||
// } | null;
|
||||
// requestedBy: {
|
||||
// fullName: string;
|
||||
// } | null;
|
||||
// }
|
||||
|
||||
export function SignaturesModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
policyRef,
|
||||
owner,
|
||||
}: SignaturesModalProps) {
|
||||
const data = useFragment<SignaturesModal_policyVersions$key>(
|
||||
policyVersionsFragment,
|
||||
policyRef
|
||||
);
|
||||
|
||||
// Safely access and extract version nodes
|
||||
const versionNodes = (data?.policyVersions?.edges?.map(edge => edge.node) || []);
|
||||
|
||||
// Filter to just published versions and sort by version number
|
||||
const publishedVersions = versionNodes
|
||||
.filter(v => v.status === "PUBLISHED")
|
||||
.sort((a, b) => b.version - a.version);
|
||||
|
||||
const [selectedVersion, setSelectedVersion] = useState<number>(
|
||||
publishedVersions[0]?.version || 0
|
||||
);
|
||||
|
||||
const selectedVersionData = versionNodes.find(v => v.version === selectedVersion);
|
||||
|
||||
const { organizationId } = useParams();
|
||||
const [queryRef, loadQuery] = useQueryLoader<SignaturesModalOrganizationQuery>(organizationQuery);
|
||||
const { toast } = useToast();
|
||||
const [isRequesting, setIsRequesting] = useState<string | null>(null);
|
||||
|
||||
const [commitRequestSignature] = useMutation<SignaturesModalRequestSignatureMutation>(requestSignatureMutation);
|
||||
|
||||
// Load organization data when the modal opens and we have a valid version selected
|
||||
useEffect(() => {
|
||||
if (isOpen && organizationId && selectedVersionData?.status === "PUBLISHED") {
|
||||
loadQuery({ organizationId });
|
||||
}
|
||||
}, [isOpen, organizationId, loadQuery, selectedVersionData]);
|
||||
|
||||
const formatDateTime = (dateString?: string | null) => {
|
||||
if (!dateString) return "N/A";
|
||||
const date = new Date(dateString);
|
||||
return format(date, "h:mm a • MMM d, yyyy");
|
||||
};
|
||||
|
||||
// Safely type signatures as our defined Signature interface
|
||||
const signatures = selectedVersionData?.signatures?.edges?.map(edge => edge.node) || [];
|
||||
|
||||
const handleRequestSignature = (personId: string) => {
|
||||
if (!selectedVersionData) return;
|
||||
|
||||
setIsRequesting(personId);
|
||||
|
||||
commitRequestSignature({
|
||||
variables: {
|
||||
input: {
|
||||
policyVersionId: selectedVersionData.id,
|
||||
signatoryId: personId,
|
||||
},
|
||||
connections: [
|
||||
ConnectionHandler.getConnectionID(
|
||||
selectedVersionData.id,
|
||||
"SignaturesModal_policyVersions_signatures"
|
||||
),
|
||||
],
|
||||
},
|
||||
onCompleted: () => {
|
||||
toast({
|
||||
title: "Success",
|
||||
description: "Signature request sent successfully",
|
||||
});
|
||||
setIsRequesting(null);
|
||||
},
|
||||
onError: (error) => {
|
||||
toast({
|
||||
title: "Error",
|
||||
description: error.message,
|
||||
variant: "destructive",
|
||||
});
|
||||
setIsRequesting(null);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="max-w-[1080px] h-[744px] p-0 overflow-hidden flex flex-col">
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
{/* Version History Sidebar */}
|
||||
<div className="w-[316px] border-r border-solid-b h-full overflow-y-auto">
|
||||
<h2 className="text-lg font-semibold p-6 pb-4">Version history</h2>
|
||||
<div className="overflow-y-auto">
|
||||
{versionNodes.length > 0 ? (
|
||||
versionNodes.map(version => {
|
||||
const isPublished = version.status === "PUBLISHED";
|
||||
const isSelected = selectedVersion === version.version;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={version.id}
|
||||
className={`flex items-center gap-3 p-6 py-4 ${
|
||||
isPublished ? 'cursor-pointer' : 'cursor-not-allowed opacity-50'
|
||||
} ${isSelected ? 'bg-slate-50' : ''}`}
|
||||
onClick={() => isPublished && setSelectedVersion(version.version)}
|
||||
>
|
||||
<Avatar className="h-10 w-10">
|
||||
<AvatarImage src="" alt={version.publishedBy?.fullName || owner?.fullName || ""} />
|
||||
<AvatarFallback>
|
||||
{(version.publishedBy?.fullName || owner?.fullName || "").charAt(0)}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium">Version {version.version}</span>
|
||||
{isPublished ? (
|
||||
<span className="text-xs px-2 py-0.5 rounded-full bg-green-100 text-green-800">
|
||||
Published
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-xs px-2 py-0.5 rounded-full bg-yellow-100 text-yellow-800">
|
||||
Draft
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-sm text-tertiary">
|
||||
{version.publishedBy?.fullName || owner?.fullName || "Unknown"} • {formatDateTime(version.publishedAt || version.updatedAt)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<div className="p-6 text-center text-tertiary">
|
||||
No versions available
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Content Area */}
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
{/* Section header */}
|
||||
<div className="p-6 border-b border-solid-b">
|
||||
<h2 className="text-2xl font-semibold mb-2">
|
||||
{selectedVersionData?.status === "PUBLISHED" ? "Signatures & Requests" : "Signatures"}
|
||||
</h2>
|
||||
{selectedVersionData?.status === "PUBLISHED" && (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Click request to ask for a signature from people in your organization
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Content area with unified people and signatures list */}
|
||||
<div className="flex-1 overflow-y-auto p-6">
|
||||
{selectedVersionData ? (
|
||||
selectedVersionData.status === "PUBLISHED" && queryRef ? (
|
||||
<PeopleAndSignaturesList
|
||||
queryRef={queryRef}
|
||||
onRequestSignature={handleRequestSignature}
|
||||
requestingId={isRequesting}
|
||||
existingSignatures={signatures}
|
||||
formatDateTime={formatDateTime}
|
||||
/>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{signatures.length > 0 ? (
|
||||
<div className="divide-y divide-border">
|
||||
{signatures.map(signature => (
|
||||
<div key={signature.id} className="py-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-3">
|
||||
<Avatar className="h-10 w-10">
|
||||
<AvatarFallback>
|
||||
{signature.signedBy?.fullName?.charAt(0) || signature.requestedBy?.fullName?.charAt(0)}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div>
|
||||
<div className="font-medium">{signature.signedBy?.fullName || signature.requestedBy?.fullName}</div>
|
||||
<div className="text-sm text-tertiary">
|
||||
{signature.state === "SIGNED"
|
||||
? `Signed on ${formatDateTime(signature.signedAt)}`
|
||||
: `Requested on ${formatDateTime(signature.requestedAt)}`}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span className={`text-xs px-2 py-0.5 rounded-full ${
|
||||
signature.state === "SIGNED"
|
||||
? 'bg-green-100 text-green-800'
|
||||
: 'bg-yellow-100 text-yellow-800'
|
||||
}`}>
|
||||
{signature.state === "SIGNED" ? "Signed" : "Pending"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-8">
|
||||
<p className="text-tertiary">No signatures available for this version</p>
|
||||
{selectedVersionData.status !== "PUBLISHED" && (
|
||||
<p className="text-sm text-muted-foreground mt-2">Only published versions can have signatures</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div className="text-center text-tertiary py-8">
|
||||
No version selected
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedVersionData?.status === "PUBLISHED" && !queryRef && (
|
||||
<div className="py-8 text-center">
|
||||
<Loader2 className="h-6 w-6 animate-spin mx-auto mb-2" />
|
||||
<p className="text-tertiary">Loading...</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end p-4 border-t border-solid-b mt-auto">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={onClose}
|
||||
className="mr-2"
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
// Combined people and signatures list component
|
||||
function PeopleAndSignaturesList({
|
||||
queryRef,
|
||||
onRequestSignature,
|
||||
requestingId,
|
||||
existingSignatures,
|
||||
formatDateTime,
|
||||
}: {
|
||||
queryRef: PreloadedQuery<SignaturesModalOrganizationQuery>,
|
||||
onRequestSignature: (personId: string) => void,
|
||||
requestingId: string | null,
|
||||
existingSignatures: Array<SignaturesModal_policyVersions$data["policyVersions"]["edges"][0]["node"]["signatures"]["edges"][0]["node"]>,
|
||||
formatDateTime: (date?: string | null) => string,
|
||||
}) {
|
||||
const data = usePreloadedQuery<SignaturesModalOrganizationQuery>(
|
||||
organizationQuery,
|
||||
queryRef
|
||||
);
|
||||
|
||||
const people = data?.organization?.peoples?.edges || [];
|
||||
|
||||
if (!data?.organization) {
|
||||
return (
|
||||
<div className="text-center py-8">
|
||||
Organization not found
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (people.length === 0) {
|
||||
return (
|
||||
<div className="text-center py-8">
|
||||
No people available to request signatures from
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Create lookup for signatures by person ID
|
||||
const signaturesByPersonId = new Map();
|
||||
existingSignatures.forEach(sig => {
|
||||
if (sig.signedBy?.id) {
|
||||
signaturesByPersonId.set(sig.signedBy.id, sig);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="divide-y divide-border">
|
||||
{people.map((edge: any) => {
|
||||
const person = edge.node;
|
||||
const signature = signaturesByPersonId.get(person.id);
|
||||
const isRequesting = requestingId === person.id;
|
||||
|
||||
return (
|
||||
<div key={person.id} className="py-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-3">
|
||||
<Avatar className="h-10 w-10">
|
||||
<AvatarFallback>
|
||||
{person.fullName.charAt(0)}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div>
|
||||
<div className="font-medium">{person.fullName}</div>
|
||||
<div className="text-sm text-tertiary">
|
||||
{signature ? (
|
||||
signature.state === "SIGNED" ? (
|
||||
<div className="flex items-center gap-1 text-green-700">
|
||||
<CheckCircle2 className="h-3.5 w-3.5" />
|
||||
<span>Signed on {formatDateTime(signature.signedAt)}</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-1 text-amber-700">
|
||||
<Clock className="h-3.5 w-3.5" />
|
||||
<span>Requested on {formatDateTime(signature.requestedAt)}</span>
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
person.primaryEmailAddress
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{signature ? (
|
||||
<span className={`text-xs px-2 py-0.5 rounded-full ${
|
||||
signature.state === "SIGNED"
|
||||
? 'bg-green-100 text-green-800'
|
||||
: 'bg-yellow-100 text-yellow-800'
|
||||
}`}>
|
||||
{signature.state === "SIGNED" ? "Signed" : "Pending"}
|
||||
</span>
|
||||
) : (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => onRequestSignature(person.id)}
|
||||
disabled={isRequesting}
|
||||
>
|
||||
{isRequesting ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
||||
Requesting...
|
||||
</>
|
||||
) : (
|
||||
"Request Signature"
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
import { Dialog, DialogContent } from "@/components/ui/dialog";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { format } from "date-fns";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useState } from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import { graphql, useFragment } from "react-relay";
|
||||
import type { VersionHistoryModal_policyVersions$key } from "./__generated__/VersionHistoryModal_policyVersions.graphql";
|
||||
|
||||
export const policyVersionsFragment = graphql`
|
||||
fragment VersionHistoryModal_policyVersions on Policy {
|
||||
title
|
||||
owner {
|
||||
fullName
|
||||
}
|
||||
versionHistory: versions(first: 20) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
version
|
||||
status
|
||||
content
|
||||
changelog
|
||||
publishedAt
|
||||
updatedAt
|
||||
publishedBy {
|
||||
fullName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface VersionHistoryModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
policyRef: VersionHistoryModal_policyVersions$key;
|
||||
onRestoreVersion?: (versionNumber: number) => void;
|
||||
}
|
||||
|
||||
export function VersionHistoryModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
policyRef,
|
||||
onRestoreVersion
|
||||
}: VersionHistoryModalProps) {
|
||||
const data = useFragment<VersionHistoryModal_policyVersions$key>(
|
||||
policyVersionsFragment,
|
||||
policyRef
|
||||
);
|
||||
|
||||
// Safely access and extract version nodes
|
||||
const versionNodes = data?.versionHistory?.edges?.map(edge => edge.node) || [];
|
||||
|
||||
// Sort versions by version number (descending)
|
||||
const versions = [...versionNodes].sort((a, b) => b.version - a.version);
|
||||
|
||||
const latestVersion = versions.length > 0 ? versions[0].version : 0;
|
||||
const [selectedVersion, setSelectedVersion] = useState<number>(latestVersion);
|
||||
|
||||
const currentVersionData = versions.find(v => v.version === selectedVersion);
|
||||
|
||||
// Format time helper for version history
|
||||
const formatDateTime = (dateString?: string | null) => {
|
||||
if (!dateString) return "N/A";
|
||||
const date = new Date(dateString);
|
||||
return format(date, "h:mm a • MMM d, yyyy");
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="max-w-[1080px] h-[744px] p-0 overflow-hidden flex flex-col">
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
{/* Version History Sidebar */}
|
||||
<div className="w-[316px] border-r border-solid-b h-full overflow-y-auto">
|
||||
<h2 className="text-lg font-semibold p-6 pb-4">Version history</h2>
|
||||
<div className="overflow-y-auto">
|
||||
{versions.map((version) => (
|
||||
<div
|
||||
key={version.id}
|
||||
className={`flex items-center gap-3 p-6 py-4 cursor-pointer ${selectedVersion === version.version ? 'bg-slate-50' : ''}`}
|
||||
onClick={() => setSelectedVersion(version.version)}
|
||||
>
|
||||
<Avatar className="h-10 w-10">
|
||||
<AvatarImage src="" alt={version.publishedBy?.fullName || data.owner?.fullName || ""} />
|
||||
<AvatarFallback>
|
||||
{(version.publishedBy?.fullName || data.owner?.fullName || "").charAt(0)}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium">Version {version.version}</span>
|
||||
{version.status === "PUBLISHED" ? (
|
||||
<span className="text-xs px-2 py-0.5 rounded-full bg-green-100 text-green-800">
|
||||
Published
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-xs px-2 py-0.5 rounded-full bg-yellow-100 text-yellow-800">
|
||||
Draft
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-sm text-tertiary">
|
||||
{version.publishedBy?.fullName || data.owner?.fullName || "Unknown"} • {formatDateTime(version.publishedAt || version.updatedAt)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content Area */}
|
||||
<div className="flex-1 p-6 relative overflow-y-auto">
|
||||
<h2 className="text-2xl font-semibold mb-6">{data.title}</h2>
|
||||
|
||||
<div className="prose prose-olive max-w-none">
|
||||
{selectedVersion && (
|
||||
<ReactMarkdown>
|
||||
{versions.find(v => v.version === selectedVersion)?.content || "No content available"}
|
||||
</ReactMarkdown>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end p-4 border-t border-solid-b mt-auto">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={onClose}
|
||||
className="mr-2"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
{onRestoreVersion && (
|
||||
<Button
|
||||
variant="default"
|
||||
onClick={() => onRestoreVersion(selectedVersion)}
|
||||
disabled={selectedVersion === latestVersion}
|
||||
>
|
||||
Restore version
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<171200736f3a443faae3f8b7c2085f4f>>
|
||||
* @generated SignedSource<<d8d76cb73e37916ddc34017550ff3702>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -9,30 +9,18 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type PolicyStatus = "ACTIVE" | "DRAFT";
|
||||
export type UpdatePolicyInput = {
|
||||
content?: string | null | undefined;
|
||||
id: string;
|
||||
name?: string | null | undefined;
|
||||
ownerId?: string | null | undefined;
|
||||
reviewDate?: string | null | undefined;
|
||||
status?: PolicyStatus | null | undefined;
|
||||
export type UpdatePolicyVersionInput = {
|
||||
content: string;
|
||||
policyVersionId: string;
|
||||
};
|
||||
export type EditPolicyViewMutation$variables = {
|
||||
input: UpdatePolicyInput;
|
||||
input: UpdatePolicyVersionInput;
|
||||
};
|
||||
export type EditPolicyViewMutation$data = {
|
||||
readonly updatePolicy: {
|
||||
readonly policy: {
|
||||
readonly updatePolicyVersion: {
|
||||
readonly policyVersion: {
|
||||
readonly content: string;
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly owner: {
|
||||
readonly fullName: string;
|
||||
readonly id: string;
|
||||
};
|
||||
readonly reviewDate: string | null | undefined;
|
||||
readonly status: PolicyStatus;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -49,14 +37,7 @@ var v0 = [
|
||||
"name": "input"
|
||||
}
|
||||
],
|
||||
v1 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
v2 = [
|
||||
v1 = [
|
||||
{
|
||||
"alias": null,
|
||||
"args": [
|
||||
@@ -66,25 +47,24 @@ v2 = [
|
||||
"variableName": "input"
|
||||
}
|
||||
],
|
||||
"concreteType": "UpdatePolicyPayload",
|
||||
"concreteType": "UpdatePolicyVersionPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "updatePolicy",
|
||||
"name": "updatePolicyVersion",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Policy",
|
||||
"concreteType": "PolicyVersion",
|
||||
"kind": "LinkedField",
|
||||
"name": "policy",
|
||||
"name": "policyVersion",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v1/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "name",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
@@ -93,39 +73,6 @@ v2 = [
|
||||
"kind": "ScalarField",
|
||||
"name": "content",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "status",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "reviewDate",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "People",
|
||||
"kind": "LinkedField",
|
||||
"name": "owner",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v1/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "fullName",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
@@ -140,7 +87,7 @@ return {
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "EditPolicyViewMutation",
|
||||
"selections": (v2/*: any*/),
|
||||
"selections": (v1/*: any*/),
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
@@ -149,19 +96,19 @@ return {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "EditPolicyViewMutation",
|
||||
"selections": (v2/*: any*/)
|
||||
"selections": (v1/*: any*/)
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "643990a3ab54cb51144f434b023531e7",
|
||||
"cacheID": "02b56043d606c6237dd502a7695fea50",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "EditPolicyViewMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation EditPolicyViewMutation(\n $input: UpdatePolicyInput!\n) {\n updatePolicy(input: $input) {\n policy {\n id\n name\n content\n status\n reviewDate\n owner {\n id\n fullName\n }\n }\n }\n}\n"
|
||||
"text": "mutation EditPolicyViewMutation(\n $input: UpdatePolicyVersionInput!\n) {\n updatePolicyVersion(input: $input) {\n policyVersion {\n id\n content\n }\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "41d4945568bc1f43eeedc727a968654d";
|
||||
(node as any).hash = "556fbcc4fadc0011c1a2154dcffeb389";
|
||||
|
||||
export default node;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<312876323f4072e43048dfe1b2e21d08>>
|
||||
* @generated SignedSource<<7738a95cc83ee25c08f6ddc556f144c1>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -10,25 +10,26 @@
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type PolicyStatus = "ACTIVE" | "DRAFT";
|
||||
export type EditPolicyViewQuery$variables = {
|
||||
organizationId: string;
|
||||
policyId: string;
|
||||
policyVersionId: string;
|
||||
};
|
||||
export type EditPolicyViewQuery$data = {
|
||||
readonly organization: {
|
||||
readonly " $fragmentSpreads": FragmentRefs<"PeopleSelector_organization">;
|
||||
};
|
||||
readonly policy: {
|
||||
readonly content?: string;
|
||||
readonly id: string;
|
||||
readonly name?: string;
|
||||
readonly owner?: {
|
||||
readonly fullName: string;
|
||||
readonly id: string;
|
||||
};
|
||||
readonly reviewDate?: string | null | undefined;
|
||||
readonly status?: PolicyStatus;
|
||||
readonly title?: string;
|
||||
};
|
||||
readonly policyVersion: {
|
||||
readonly content?: string;
|
||||
readonly id: string;
|
||||
};
|
||||
};
|
||||
export type EditPolicyViewQuery = {
|
||||
@@ -47,56 +48,61 @@ v1 = {
|
||||
"kind": "LocalArgument",
|
||||
"name": "policyId"
|
||||
},
|
||||
v2 = [
|
||||
v2 = {
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "policyVersionId"
|
||||
},
|
||||
v3 = [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "id",
|
||||
"variableName": "policyId"
|
||||
"variableName": "policyVersionId"
|
||||
}
|
||||
],
|
||||
v3 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
v4 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "fullName",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
v5 = {
|
||||
"kind": "InlineFragment",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "name",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "content",
|
||||
"storageKey": null
|
||||
},
|
||||
}
|
||||
],
|
||||
"type": "PolicyVersion",
|
||||
"abstractKey": null
|
||||
},
|
||||
v6 = [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "id",
|
||||
"variableName": "policyId"
|
||||
}
|
||||
],
|
||||
v7 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "fullName",
|
||||
"storageKey": null
|
||||
},
|
||||
v8 = {
|
||||
"kind": "InlineFragment",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "status",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "reviewDate",
|
||||
"name": "title",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
@@ -107,8 +113,8 @@ v5 = {
|
||||
"name": "owner",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
(v4/*: any*/)
|
||||
(v4/*: any*/),
|
||||
(v7/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
@@ -116,21 +122,21 @@ v5 = {
|
||||
"type": "Policy",
|
||||
"abstractKey": null
|
||||
},
|
||||
v6 = [
|
||||
v9 = [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "id",
|
||||
"variableName": "organizationId"
|
||||
}
|
||||
],
|
||||
v7 = {
|
||||
v10 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__typename",
|
||||
"storageKey": null
|
||||
},
|
||||
v8 = [
|
||||
v11 = [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
@@ -149,32 +155,46 @@ return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": [
|
||||
(v0/*: any*/),
|
||||
(v1/*: any*/)
|
||||
(v1/*: any*/),
|
||||
(v2/*: any*/)
|
||||
],
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "EditPolicyViewQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": "policy",
|
||||
"args": (v2/*: any*/),
|
||||
"alias": "policyVersion",
|
||||
"args": (v3/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
(v4/*: any*/),
|
||||
(v5/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": "organization",
|
||||
"alias": "policy",
|
||||
"args": (v6/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v4/*: any*/),
|
||||
(v8/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": "organization",
|
||||
"args": (v9/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"args": null,
|
||||
@@ -192,41 +212,56 @@ return {
|
||||
"operation": {
|
||||
"argumentDefinitions": [
|
||||
(v1/*: any*/),
|
||||
(v0/*: any*/)
|
||||
(v0/*: any*/),
|
||||
(v2/*: any*/)
|
||||
],
|
||||
"kind": "Operation",
|
||||
"name": "EditPolicyViewQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": "policy",
|
||||
"args": (v2/*: any*/),
|
||||
"alias": "policyVersion",
|
||||
"args": (v3/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v7/*: any*/),
|
||||
(v3/*: any*/),
|
||||
(v10/*: any*/),
|
||||
(v4/*: any*/),
|
||||
(v5/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": "organization",
|
||||
"alias": "policy",
|
||||
"args": (v6/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v7/*: any*/),
|
||||
(v3/*: any*/),
|
||||
(v10/*: any*/),
|
||||
(v4/*: any*/),
|
||||
(v8/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": "organization",
|
||||
"args": (v9/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v10/*: any*/),
|
||||
(v4/*: any*/),
|
||||
{
|
||||
"kind": "InlineFragment",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v8/*: any*/),
|
||||
"args": (v11/*: any*/),
|
||||
"concreteType": "PeopleConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "peoples",
|
||||
@@ -248,8 +283,8 @@ return {
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
(v4/*: any*/),
|
||||
(v7/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
@@ -257,7 +292,7 @@ return {
|
||||
"name": "primaryEmailAddress",
|
||||
"storageKey": null
|
||||
},
|
||||
(v7/*: any*/)
|
||||
(v10/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
@@ -301,7 +336,7 @@ return {
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v8/*: any*/),
|
||||
"args": (v11/*: any*/),
|
||||
"filters": [
|
||||
"orderBy"
|
||||
],
|
||||
@@ -320,16 +355,16 @@ return {
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "4d52d22f9d8acc7912a57d974f558248",
|
||||
"cacheID": "4d586701fc5f2e4976bb87d9be61c594",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "EditPolicyViewQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query EditPolicyViewQuery(\n $policyId: ID!\n $organizationId: ID!\n) {\n policy: node(id: $policyId) {\n __typename\n id\n ... on Policy {\n name\n content\n status\n reviewDate\n owner {\n id\n fullName\n }\n }\n }\n organization: node(id: $organizationId) {\n __typename\n ...PeopleSelector_organization\n id\n }\n}\n\nfragment PeopleSelector_organization on Organization {\n id\n peoples(first: 100, orderBy: {direction: ASC, field: FULL_NAME}) {\n edges {\n node {\n id\n fullName\n primaryEmailAddress\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n"
|
||||
"text": "query EditPolicyViewQuery(\n $policyId: ID!\n $organizationId: ID!\n $policyVersionId: ID!\n) {\n policyVersion: node(id: $policyVersionId) {\n __typename\n id\n ... on PolicyVersion {\n content\n }\n }\n policy: node(id: $policyId) {\n __typename\n id\n ... on Policy {\n title\n owner {\n id\n fullName\n }\n }\n }\n organization: node(id: $organizationId) {\n __typename\n ...PeopleSelector_organization\n id\n }\n}\n\nfragment PeopleSelector_organization on Organization {\n id\n peoples(first: 100, orderBy: {direction: ASC, field: FULL_NAME}) {\n edges {\n node {\n id\n fullName\n primaryEmailAddress\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "d16bdd881b27b1aa5368130ce036fd20";
|
||||
(node as any).hash = "2a934195ed33bd073e3976e8cc19a925";
|
||||
|
||||
export default node;
|
||||
|
||||
@@ -1,230 +0,0 @@
|
||||
/**
|
||||
* @generated SignedSource<<84789bb949f94e924de1c9b9d566b4a5>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type NewPolicyViewQuery$variables = {
|
||||
organizationId: string;
|
||||
};
|
||||
export type NewPolicyViewQuery$data = {
|
||||
readonly organization: {
|
||||
readonly " $fragmentSpreads": FragmentRefs<"PeopleSelector_organization">;
|
||||
};
|
||||
};
|
||||
export type NewPolicyViewQuery = {
|
||||
response: NewPolicyViewQuery$data;
|
||||
variables: NewPolicyViewQuery$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = [
|
||||
{
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "organizationId"
|
||||
}
|
||||
],
|
||||
v1 = [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "id",
|
||||
"variableName": "organizationId"
|
||||
}
|
||||
],
|
||||
v2 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__typename",
|
||||
"storageKey": null
|
||||
},
|
||||
v3 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
v4 = [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 100
|
||||
},
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "orderBy",
|
||||
"value": {
|
||||
"direction": "ASC",
|
||||
"field": "FULL_NAME"
|
||||
}
|
||||
}
|
||||
];
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "NewPolicyViewQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": "organization",
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"args": null,
|
||||
"kind": "FragmentSpread",
|
||||
"name": "PeopleSelector_organization"
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Query",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "NewPolicyViewQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": "organization",
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"kind": "InlineFragment",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v4/*: any*/),
|
||||
"concreteType": "PeopleConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "peoples",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PeopleEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "People",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "fullName",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "primaryEmailAddress",
|
||||
"storageKey": null
|
||||
},
|
||||
(v2/*: 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
|
||||
}
|
||||
],
|
||||
"storageKey": "peoples(first:100,orderBy:{\"direction\":\"ASC\",\"field\":\"FULL_NAME\"})"
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v4/*: any*/),
|
||||
"filters": [
|
||||
"orderBy"
|
||||
],
|
||||
"handle": "connection",
|
||||
"key": "PeopleSelector_organization_peoples",
|
||||
"kind": "LinkedHandle",
|
||||
"name": "peoples"
|
||||
}
|
||||
],
|
||||
"type": "Organization",
|
||||
"abstractKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "552067b4b0632972c14f7a8dcaf171aa",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "NewPolicyViewQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query NewPolicyViewQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ...PeopleSelector_organization\n id\n }\n}\n\nfragment PeopleSelector_organization on Organization {\n id\n peoples(first: 100, orderBy: {direction: ASC, field: FULL_NAME}) {\n edges {\n node {\n id\n fullName\n primaryEmailAddress\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "98fa41bfaf0d76adce1c91cf8d6ade28";
|
||||
|
||||
export default node;
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<2026b385ee5eb895da6ca9c919762913>>
|
||||
* @generated SignedSource<<885dc2a54ba3b8d4663e197cfe61d704>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -9,39 +9,36 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type PolicyStatus = "ACTIVE" | "DRAFT";
|
||||
export type CreatePolicyInput = {
|
||||
content: string;
|
||||
name: string;
|
||||
organizationId: string;
|
||||
ownerId: string;
|
||||
reviewDate?: string | null | undefined;
|
||||
status: PolicyStatus;
|
||||
title: string;
|
||||
};
|
||||
export type NewPolicyViewMutation$variables = {
|
||||
export type PolicyListViewCreateMutation$variables = {
|
||||
connections: ReadonlyArray<string>;
|
||||
input: CreatePolicyInput;
|
||||
};
|
||||
export type NewPolicyViewMutation$data = {
|
||||
export type PolicyListViewCreateMutation$data = {
|
||||
readonly createPolicy: {
|
||||
readonly policyEdge: {
|
||||
readonly node: {
|
||||
readonly content: string;
|
||||
readonly createdAt: string;
|
||||
readonly description: string;
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly owner: {
|
||||
readonly fullName: string;
|
||||
readonly id: string;
|
||||
};
|
||||
readonly reviewDate: string | null | undefined;
|
||||
readonly status: PolicyStatus;
|
||||
readonly title: string;
|
||||
readonly updatedAt: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
export type NewPolicyViewMutation = {
|
||||
response: NewPolicyViewMutation$data;
|
||||
variables: NewPolicyViewMutation$variables;
|
||||
export type PolicyListViewCreateMutation = {
|
||||
response: PolicyListViewCreateMutation$data;
|
||||
variables: PolicyListViewCreateMutation$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
@@ -90,28 +87,28 @@ v4 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "name",
|
||||
"name": "title",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "content",
|
||||
"name": "description",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "status",
|
||||
"name": "createdAt",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "reviewDate",
|
||||
"name": "updatedAt",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
@@ -147,7 +144,7 @@ return {
|
||||
],
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "NewPolicyViewMutation",
|
||||
"name": "PolicyListViewCreateMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
@@ -172,7 +169,7 @@ return {
|
||||
(v0/*: any*/)
|
||||
],
|
||||
"kind": "Operation",
|
||||
"name": "NewPolicyViewMutation",
|
||||
"name": "PolicyListViewCreateMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
@@ -205,16 +202,16 @@ return {
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "aa29cde3106d1c8d35c6ae8e74f310b7",
|
||||
"cacheID": "f7aae6d7a0d276e71caec643e43b5ee5",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "NewPolicyViewMutation",
|
||||
"name": "PolicyListViewCreateMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation NewPolicyViewMutation(\n $input: CreatePolicyInput!\n) {\n createPolicy(input: $input) {\n policyEdge {\n node {\n id\n name\n content\n status\n reviewDate\n owner {\n id\n fullName\n }\n }\n }\n }\n}\n"
|
||||
"text": "mutation PolicyListViewCreateMutation(\n $input: CreatePolicyInput!\n) {\n createPolicy(input: $input) {\n policyEdge {\n node {\n id\n title\n description\n createdAt\n updatedAt\n owner {\n id\n fullName\n }\n }\n }\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "f6dcabf6c51e3858c9750e00fcc0cc15";
|
||||
(node as any).hash = "d55ac010a7ad352d830d3a38f2f8d5a8";
|
||||
|
||||
export default node;
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<391e3817107bb96f50aad61d20cad0a8>>
|
||||
* @generated SignedSource<<5b24e53258fceb03535363014f57a15e>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -9,7 +9,8 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type PolicyStatus = "ACTIVE" | "DRAFT";
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type PolicyStatus = "DRAFT" | "PUBLISHED";
|
||||
export type PolicyListViewQuery$variables = {
|
||||
organizationId: string;
|
||||
};
|
||||
@@ -18,15 +19,34 @@ export type PolicyListViewQuery$data = {
|
||||
readonly policies?: {
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly content: string;
|
||||
readonly createdAt: string;
|
||||
readonly currentPublishedVersion: number | null | undefined;
|
||||
readonly description: string;
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly status: PolicyStatus;
|
||||
readonly owner: {
|
||||
readonly fullName: string;
|
||||
readonly id: string;
|
||||
};
|
||||
readonly title: string;
|
||||
readonly updatedAt: string;
|
||||
readonly versions: {
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly id: string;
|
||||
readonly status: PolicyStatus;
|
||||
readonly updatedAt: string;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
};
|
||||
}>;
|
||||
};
|
||||
readonly " $fragmentSpreads": FragmentRefs<"PeopleSelector_organization">;
|
||||
};
|
||||
readonly viewer: {
|
||||
readonly user: {
|
||||
readonly id: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
export type PolicyListViewQuery = {
|
||||
@@ -42,28 +62,94 @@ var v0 = [
|
||||
"name": "organizationId"
|
||||
}
|
||||
],
|
||||
v1 = [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "id",
|
||||
"variableName": "organizationId"
|
||||
}
|
||||
],
|
||||
v2 = {
|
||||
v1 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
v3 = {
|
||||
v2 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "User",
|
||||
"kind": "LinkedField",
|
||||
"name": "user",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v1/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
v3 = [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "id",
|
||||
"variableName": "organizationId"
|
||||
}
|
||||
],
|
||||
v4 = {
|
||||
"kind": "Literal",
|
||||
"name": "orderBy",
|
||||
"value": {
|
||||
"direction": "ASC",
|
||||
"field": "TITLE"
|
||||
}
|
||||
},
|
||||
v5 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "updatedAt",
|
||||
"storageKey": null
|
||||
},
|
||||
v6 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "fullName",
|
||||
"storageKey": null
|
||||
},
|
||||
v7 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__typename",
|
||||
"storageKey": null
|
||||
},
|
||||
v4 = [
|
||||
v8 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "cursor",
|
||||
"storageKey": null
|
||||
},
|
||||
v9 = {
|
||||
"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
|
||||
},
|
||||
v10 = [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
@@ -80,19 +166,26 @@ v4 = [
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
(v1/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "name",
|
||||
"name": "title",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "content",
|
||||
"name": "description",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "currentPublishedVersion",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
@@ -102,66 +195,103 @@ v4 = [
|
||||
"name": "createdAt",
|
||||
"storageKey": null
|
||||
},
|
||||
(v5/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "updatedAt",
|
||||
"concreteType": "People",
|
||||
"kind": "LinkedField",
|
||||
"name": "owner",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v1/*: any*/),
|
||||
(v6/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "status",
|
||||
"storageKey": null
|
||||
"args": [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 1
|
||||
}
|
||||
],
|
||||
"concreteType": "PolicyVersionConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "versions",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PolicyVersionEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PolicyVersion",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v1/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "status",
|
||||
"storageKey": null
|
||||
},
|
||||
(v5/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": "versions(first:1)"
|
||||
},
|
||||
(v3/*: any*/)
|
||||
(v7/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "cursor",
|
||||
"storageKey": null
|
||||
}
|
||||
(v8/*: any*/)
|
||||
],
|
||||
"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
|
||||
}
|
||||
(v9/*: any*/)
|
||||
],
|
||||
v5 = [
|
||||
v11 = [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 100
|
||||
},
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "orderBy",
|
||||
"value": {
|
||||
"direction": "ASC",
|
||||
"field": "FULL_NAME"
|
||||
}
|
||||
}
|
||||
],
|
||||
v12 = [
|
||||
"orderBy"
|
||||
],
|
||||
v13 = [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 50
|
||||
},
|
||||
(v4/*: any*/)
|
||||
];
|
||||
return {
|
||||
"fragment": {
|
||||
@@ -170,9 +300,21 @@ return {
|
||||
"metadata": null,
|
||||
"name": "PolicyListViewQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Viewer",
|
||||
"kind": "LinkedField",
|
||||
"name": "viewer",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": "organization",
|
||||
"args": (v1/*: any*/),
|
||||
"args": (v3/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
@@ -182,14 +324,21 @@ return {
|
||||
"kind": "InlineFragment",
|
||||
"selections": [
|
||||
{
|
||||
"alias": "policies",
|
||||
"args": null,
|
||||
"kind": "FragmentSpread",
|
||||
"name": "PeopleSelector_organization"
|
||||
},
|
||||
{
|
||||
"alias": "policies",
|
||||
"args": [
|
||||
(v4/*: any*/)
|
||||
],
|
||||
"concreteType": "PolicyConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "__PolicyListView_policies_connection",
|
||||
"plural": false,
|
||||
"selections": (v4/*: any*/),
|
||||
"storageKey": null
|
||||
"selections": (v10/*: any*/),
|
||||
"storageKey": "__PolicyListView_policies_connection(orderBy:{\"direction\":\"ASC\",\"field\":\"TITLE\"})"
|
||||
}
|
||||
],
|
||||
"type": "Organization",
|
||||
@@ -208,32 +357,100 @@ return {
|
||||
"kind": "Operation",
|
||||
"name": "PolicyListViewQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Viewer",
|
||||
"kind": "LinkedField",
|
||||
"name": "viewer",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
(v1/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": "organization",
|
||||
"args": (v1/*: any*/),
|
||||
"args": (v3/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
(v7/*: any*/),
|
||||
(v1/*: any*/),
|
||||
{
|
||||
"kind": "InlineFragment",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v5/*: any*/),
|
||||
"args": (v11/*: any*/),
|
||||
"concreteType": "PeopleConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "peoples",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PeopleEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "People",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v1/*: any*/),
|
||||
(v6/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "primaryEmailAddress",
|
||||
"storageKey": null
|
||||
},
|
||||
(v7/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v8/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v9/*: any*/)
|
||||
],
|
||||
"storageKey": "peoples(first:100,orderBy:{\"direction\":\"ASC\",\"field\":\"FULL_NAME\"})"
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v11/*: any*/),
|
||||
"filters": (v12/*: any*/),
|
||||
"handle": "connection",
|
||||
"key": "PeopleSelector_organization_peoples",
|
||||
"kind": "LinkedHandle",
|
||||
"name": "peoples"
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v13/*: any*/),
|
||||
"concreteType": "PolicyConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "policies",
|
||||
"plural": false,
|
||||
"selections": (v4/*: any*/),
|
||||
"storageKey": "policies(first:100)"
|
||||
"selections": (v10/*: any*/),
|
||||
"storageKey": "policies(first:50,orderBy:{\"direction\":\"ASC\",\"field\":\"TITLE\"})"
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v5/*: any*/),
|
||||
"filters": null,
|
||||
"args": (v13/*: any*/),
|
||||
"filters": (v12/*: any*/),
|
||||
"handle": "connection",
|
||||
"key": "PolicyListView_policies",
|
||||
"kind": "LinkedHandle",
|
||||
@@ -242,15 +459,14 @@ return {
|
||||
],
|
||||
"type": "Organization",
|
||||
"abstractKey": null
|
||||
},
|
||||
(v2/*: any*/)
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "6434c8135f68ac64eb2f06983ddc3595",
|
||||
"cacheID": "ed650f54cc226d3adea27a78a777890c",
|
||||
"id": null,
|
||||
"metadata": {
|
||||
"connection": [
|
||||
@@ -267,11 +483,11 @@ return {
|
||||
},
|
||||
"name": "PolicyListViewQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query PolicyListViewQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n policies(first: 100) {\n edges {\n node {\n id\n name\n content\n createdAt\n updatedAt\n status\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n id\n }\n}\n"
|
||||
"text": "query PolicyListViewQuery(\n $organizationId: ID!\n) {\n viewer {\n user {\n id\n }\n id\n }\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n ...PeopleSelector_organization\n policies(first: 50, orderBy: {field: TITLE, direction: ASC}) {\n edges {\n node {\n id\n title\n description\n currentPublishedVersion\n createdAt\n updatedAt\n owner {\n id\n fullName\n }\n versions(first: 1) {\n edges {\n node {\n id\n status\n updatedAt\n }\n }\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n id\n }\n}\n\nfragment PeopleSelector_organization on Organization {\n id\n peoples(first: 100, orderBy: {direction: ASC, field: FULL_NAME}) {\n edges {\n node {\n id\n fullName\n primaryEmailAddress\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "5567339ad9b2be90b94edc8b1a16fe1e";
|
||||
(node as any).hash = "b3b0798be0779c09013a4743d015e87f";
|
||||
|
||||
export default node;
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
/**
|
||||
* @generated SignedSource<<4e559f39bd9ec2b3782113ef034cad03>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type DeletePolicyInput = {
|
||||
policyId: string;
|
||||
};
|
||||
export type PolicyViewDeleteMutation$variables = {
|
||||
connections: ReadonlyArray<string>;
|
||||
input: DeletePolicyInput;
|
||||
};
|
||||
export type PolicyViewDeleteMutation$data = {
|
||||
readonly deletePolicy: {
|
||||
readonly deletedPolicyId: string;
|
||||
};
|
||||
};
|
||||
export type PolicyViewDeleteMutation = {
|
||||
response: PolicyViewDeleteMutation$data;
|
||||
variables: PolicyViewDeleteMutation$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = {
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "connections"
|
||||
},
|
||||
v1 = {
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "input"
|
||||
},
|
||||
v2 = [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "input",
|
||||
"variableName": "input"
|
||||
}
|
||||
],
|
||||
v3 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "deletedPolicyId",
|
||||
"storageKey": null
|
||||
};
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": [
|
||||
(v0/*: any*/),
|
||||
(v1/*: any*/)
|
||||
],
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "PolicyViewDeleteMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "DeletePolicyPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "deletePolicy",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": [
|
||||
(v1/*: any*/),
|
||||
(v0/*: any*/)
|
||||
],
|
||||
"kind": "Operation",
|
||||
"name": "PolicyViewDeleteMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "DeletePolicyPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "deletePolicy",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"filters": null,
|
||||
"handle": "deleteEdge",
|
||||
"key": "",
|
||||
"kind": "ScalarHandle",
|
||||
"name": "deletedPolicyId",
|
||||
"handleArgs": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "connections",
|
||||
"variableName": "connections"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "67e67147e0c7ec0489346b1ad86899c9",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "PolicyViewDeleteMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation PolicyViewDeleteMutation(\n $input: DeletePolicyInput!\n) {\n deletePolicy(input: $input) {\n deletedPolicyId\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "7d421e9b068f3f676a4c3069769b1c18";
|
||||
|
||||
export default node;
|
||||
@@ -1,199 +0,0 @@
|
||||
/**
|
||||
* @generated SignedSource<<eaeef5694bdd615c03bea3b3f8957df3>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type PolicyStatus = "ACTIVE" | "DRAFT";
|
||||
export type PolicyViewQuery$variables = {
|
||||
policyId: string;
|
||||
};
|
||||
export type PolicyViewQuery$data = {
|
||||
readonly node: {
|
||||
readonly content?: string;
|
||||
readonly createdAt?: string;
|
||||
readonly id: string;
|
||||
readonly name?: string;
|
||||
readonly owner?: {
|
||||
readonly fullName: string;
|
||||
readonly id: string;
|
||||
readonly primaryEmailAddress: string;
|
||||
};
|
||||
readonly reviewDate?: string | null | undefined;
|
||||
readonly status?: PolicyStatus;
|
||||
readonly updatedAt?: string;
|
||||
};
|
||||
};
|
||||
export type PolicyViewQuery = {
|
||||
response: PolicyViewQuery$data;
|
||||
variables: PolicyViewQuery$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = [
|
||||
{
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "policyId"
|
||||
}
|
||||
],
|
||||
v1 = [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "id",
|
||||
"variableName": "policyId"
|
||||
}
|
||||
],
|
||||
v2 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
v3 = {
|
||||
"kind": "InlineFragment",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "name",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "content",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "createdAt",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "updatedAt",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "reviewDate",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "status",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "People",
|
||||
"kind": "LinkedField",
|
||||
"name": "owner",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "fullName",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "primaryEmailAddress",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Policy",
|
||||
"abstractKey": null
|
||||
};
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "PolicyViewQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
(v3/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Query",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "PolicyViewQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__typename",
|
||||
"storageKey": null
|
||||
},
|
||||
(v2/*: any*/),
|
||||
(v3/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "26bf57eeab9c600a3146e01085ecae8e",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "PolicyViewQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query PolicyViewQuery(\n $policyId: ID!\n) {\n node(id: $policyId) {\n __typename\n id\n ... on Policy {\n name\n content\n createdAt\n updatedAt\n reviewDate\n status\n owner {\n id\n fullName\n primaryEmailAddress\n }\n }\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "0fcc28fc290df69d14d7d549bd09216f";
|
||||
|
||||
export default node;
|
||||
135
apps/console/src/pages/organizations/policies/__generated__/ShowPolicyViewCreateDraftMutation.graphql.ts
generated
Normal file
135
apps/console/src/pages/organizations/policies/__generated__/ShowPolicyViewCreateDraftMutation.graphql.ts
generated
Normal file
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* @generated SignedSource<<290d3c4fb382c7b99c60eeee5cfa8c5a>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type PolicyStatus = "DRAFT" | "PUBLISHED";
|
||||
export type CreateDraftPolicyVersionInput = {
|
||||
policyID: string;
|
||||
};
|
||||
export type ShowPolicyViewCreateDraftMutation$variables = {
|
||||
input: CreateDraftPolicyVersionInput;
|
||||
};
|
||||
export type ShowPolicyViewCreateDraftMutation$data = {
|
||||
readonly createDraftPolicyVersion: {
|
||||
readonly policyVersionEdge: {
|
||||
readonly node: {
|
||||
readonly id: string;
|
||||
readonly status: PolicyStatus;
|
||||
readonly version: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
export type ShowPolicyViewCreateDraftMutation = {
|
||||
response: ShowPolicyViewCreateDraftMutation$data;
|
||||
variables: ShowPolicyViewCreateDraftMutation$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = [
|
||||
{
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "input"
|
||||
}
|
||||
],
|
||||
v1 = [
|
||||
{
|
||||
"alias": null,
|
||||
"args": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "input",
|
||||
"variableName": "input"
|
||||
}
|
||||
],
|
||||
"concreteType": "CreateDraftPolicyVersionPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "createDraftPolicyVersion",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PolicyVersionEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "policyVersionEdge",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PolicyVersion",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "version",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "status",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
];
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "ShowPolicyViewCreateDraftMutation",
|
||||
"selections": (v1/*: any*/),
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "ShowPolicyViewCreateDraftMutation",
|
||||
"selections": (v1/*: any*/)
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "e96d226e93260cd989dc122193fbcbce",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "ShowPolicyViewCreateDraftMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation ShowPolicyViewCreateDraftMutation(\n $input: CreateDraftPolicyVersionInput!\n) {\n createDraftPolicyVersion(input: $input) {\n policyVersionEdge {\n node {\n id\n version\n status\n }\n }\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "49282edca41fc2bdca1a1c6db4b2dc11";
|
||||
|
||||
export default node;
|
||||
211
apps/console/src/pages/organizations/policies/__generated__/ShowPolicyViewPublishMutation.graphql.ts
generated
Normal file
211
apps/console/src/pages/organizations/policies/__generated__/ShowPolicyViewPublishMutation.graphql.ts
generated
Normal file
@@ -0,0 +1,211 @@
|
||||
/**
|
||||
* @generated SignedSource<<024ec8d58aa5f37adad3ac5e98c15a90>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type PolicyStatus = "DRAFT" | "PUBLISHED";
|
||||
export type PublishPolicyVersionInput = {
|
||||
policyId: string;
|
||||
};
|
||||
export type ShowPolicyViewPublishMutation$variables = {
|
||||
input: PublishPolicyVersionInput;
|
||||
};
|
||||
export type ShowPolicyViewPublishMutation$data = {
|
||||
readonly publishPolicyVersion: {
|
||||
readonly policy: {
|
||||
readonly currentPublishedVersion: number | null | undefined;
|
||||
readonly id: string;
|
||||
};
|
||||
readonly policyVersion: {
|
||||
readonly id: string;
|
||||
readonly publishedAt: string | null | undefined;
|
||||
readonly publishedBy: {
|
||||
readonly fullName: string;
|
||||
} | null | undefined;
|
||||
readonly status: PolicyStatus;
|
||||
};
|
||||
};
|
||||
};
|
||||
export type ShowPolicyViewPublishMutation = {
|
||||
response: ShowPolicyViewPublishMutation$data;
|
||||
variables: ShowPolicyViewPublishMutation$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = [
|
||||
{
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "input"
|
||||
}
|
||||
],
|
||||
v1 = [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "input",
|
||||
"variableName": "input"
|
||||
}
|
||||
],
|
||||
v2 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
v3 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Policy",
|
||||
"kind": "LinkedField",
|
||||
"name": "policy",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "currentPublishedVersion",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
v4 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "status",
|
||||
"storageKey": null
|
||||
},
|
||||
v5 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "publishedAt",
|
||||
"storageKey": null
|
||||
},
|
||||
v6 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "fullName",
|
||||
"storageKey": null
|
||||
};
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "ShowPolicyViewPublishMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": "PublishPolicyVersionPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "publishPolicyVersion",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PolicyVersion",
|
||||
"kind": "LinkedField",
|
||||
"name": "policyVersion",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
(v4/*: any*/),
|
||||
(v5/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "People",
|
||||
"kind": "LinkedField",
|
||||
"name": "publishedBy",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v6/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "ShowPolicyViewPublishMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": "PublishPolicyVersionPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "publishPolicyVersion",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PolicyVersion",
|
||||
"kind": "LinkedField",
|
||||
"name": "policyVersion",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
(v4/*: any*/),
|
||||
(v5/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "People",
|
||||
"kind": "LinkedField",
|
||||
"name": "publishedBy",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v6/*: any*/),
|
||||
(v2/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "2047a011abeae6642230cb7b31080b08",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "ShowPolicyViewPublishMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation ShowPolicyViewPublishMutation(\n $input: PublishPolicyVersionInput!\n) {\n publishPolicyVersion(input: $input) {\n policy {\n id\n currentPublishedVersion\n }\n policyVersion {\n id\n status\n publishedAt\n publishedBy {\n fullName\n id\n }\n }\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "d81485a797858cf5f2834cf131d248cc";
|
||||
|
||||
export default node;
|
||||
606
apps/console/src/pages/organizations/policies/__generated__/ShowPolicyViewQuery.graphql.ts
generated
Normal file
606
apps/console/src/pages/organizations/policies/__generated__/ShowPolicyViewQuery.graphql.ts
generated
Normal file
@@ -0,0 +1,606 @@
|
||||
/**
|
||||
* @generated SignedSource<<351444982c4868267f30cc9fc8a2399a>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type PolicyStatus = "DRAFT" | "PUBLISHED";
|
||||
export type ShowPolicyViewQuery$variables = {
|
||||
policyId: string;
|
||||
};
|
||||
export type ShowPolicyViewQuery$data = {
|
||||
readonly node: {
|
||||
readonly createdAt?: string;
|
||||
readonly currentPublishedVersion?: number | null | undefined;
|
||||
readonly description?: string;
|
||||
readonly id: string;
|
||||
readonly latestVersion?: {
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly changelog: string;
|
||||
readonly content: string;
|
||||
readonly createdAt: string;
|
||||
readonly id: string;
|
||||
readonly publishedAt: string | null | undefined;
|
||||
readonly publishedBy: {
|
||||
readonly fullName: string;
|
||||
} | null | undefined;
|
||||
readonly status: PolicyStatus;
|
||||
readonly updatedAt: string;
|
||||
readonly version: number;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
readonly owner?: {
|
||||
readonly fullName: string;
|
||||
readonly id: string;
|
||||
readonly primaryEmailAddress: string;
|
||||
};
|
||||
readonly title?: string;
|
||||
readonly updatedAt?: string;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"SignaturesModal_policyVersions" | "VersionHistoryModal_policyVersions">;
|
||||
};
|
||||
};
|
||||
export type ShowPolicyViewQuery = {
|
||||
response: ShowPolicyViewQuery$data;
|
||||
variables: ShowPolicyViewQuery$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = [
|
||||
{
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "policyId"
|
||||
}
|
||||
],
|
||||
v1 = [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "id",
|
||||
"variableName": "policyId"
|
||||
}
|
||||
],
|
||||
v2 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
v3 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "title",
|
||||
"storageKey": null
|
||||
},
|
||||
v4 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "description",
|
||||
"storageKey": null
|
||||
},
|
||||
v5 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "createdAt",
|
||||
"storageKey": null
|
||||
},
|
||||
v6 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "updatedAt",
|
||||
"storageKey": null
|
||||
},
|
||||
v7 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "currentPublishedVersion",
|
||||
"storageKey": null
|
||||
},
|
||||
v8 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "fullName",
|
||||
"storageKey": null
|
||||
},
|
||||
v9 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "People",
|
||||
"kind": "LinkedField",
|
||||
"name": "owner",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
(v8/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "primaryEmailAddress",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
v10 = [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 1
|
||||
}
|
||||
],
|
||||
v11 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "version",
|
||||
"storageKey": null
|
||||
},
|
||||
v12 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "status",
|
||||
"storageKey": null
|
||||
},
|
||||
v13 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "content",
|
||||
"storageKey": null
|
||||
},
|
||||
v14 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "changelog",
|
||||
"storageKey": null
|
||||
},
|
||||
v15 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "publishedAt",
|
||||
"storageKey": null
|
||||
},
|
||||
v16 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__typename",
|
||||
"storageKey": null
|
||||
},
|
||||
v17 = [
|
||||
(v8/*: any*/),
|
||||
(v2/*: any*/)
|
||||
],
|
||||
v18 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "People",
|
||||
"kind": "LinkedField",
|
||||
"name": "publishedBy",
|
||||
"plural": false,
|
||||
"selections": (v17/*: any*/),
|
||||
"storageKey": null
|
||||
},
|
||||
v19 = [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 100
|
||||
}
|
||||
];
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "ShowPolicyViewQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"kind": "InlineFragment",
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
(v4/*: any*/),
|
||||
(v5/*: any*/),
|
||||
(v6/*: any*/),
|
||||
(v7/*: any*/),
|
||||
(v9/*: any*/),
|
||||
{
|
||||
"args": null,
|
||||
"kind": "FragmentSpread",
|
||||
"name": "SignaturesModal_policyVersions"
|
||||
},
|
||||
{
|
||||
"args": null,
|
||||
"kind": "FragmentSpread",
|
||||
"name": "VersionHistoryModal_policyVersions"
|
||||
},
|
||||
{
|
||||
"alias": "latestVersion",
|
||||
"args": (v10/*: any*/),
|
||||
"concreteType": "PolicyVersionConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "versions",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PolicyVersionEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PolicyVersion",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
(v11/*: any*/),
|
||||
(v12/*: any*/),
|
||||
(v13/*: any*/),
|
||||
(v14/*: any*/),
|
||||
(v15/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "People",
|
||||
"kind": "LinkedField",
|
||||
"name": "publishedBy",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v8/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v5/*: any*/),
|
||||
(v6/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": "versions(first:1)"
|
||||
}
|
||||
],
|
||||
"type": "Policy",
|
||||
"abstractKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Query",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "ShowPolicyViewQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v16/*: any*/),
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"kind": "InlineFragment",
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
(v4/*: any*/),
|
||||
(v5/*: any*/),
|
||||
(v6/*: any*/),
|
||||
(v7/*: any*/),
|
||||
(v9/*: any*/),
|
||||
{
|
||||
"alias": "policyVersions",
|
||||
"args": [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 10
|
||||
}
|
||||
],
|
||||
"concreteType": "PolicyVersionConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "versions",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PolicyVersionEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PolicyVersion",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
(v11/*: any*/),
|
||||
(v12/*: any*/),
|
||||
(v15/*: any*/),
|
||||
(v6/*: any*/),
|
||||
(v18/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v19/*: any*/),
|
||||
"concreteType": "PolicyVersionSignatureConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "signatures",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PolicyVersionSignatureEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PolicyVersionSignature",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "state",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "signedAt",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "requestedAt",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "People",
|
||||
"kind": "LinkedField",
|
||||
"name": "signedBy",
|
||||
"plural": false,
|
||||
"selections": (v17/*: any*/),
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "People",
|
||||
"kind": "LinkedField",
|
||||
"name": "requestedBy",
|
||||
"plural": false,
|
||||
"selections": (v17/*: any*/),
|
||||
"storageKey": null
|
||||
},
|
||||
(v16/*: 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
|
||||
}
|
||||
],
|
||||
"storageKey": "signatures(first:100)"
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v19/*: any*/),
|
||||
"filters": null,
|
||||
"handle": "connection",
|
||||
"key": "SignaturesModal_policyVersions_signatures",
|
||||
"kind": "LinkedHandle",
|
||||
"name": "signatures"
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": "versions(first:10)"
|
||||
},
|
||||
{
|
||||
"alias": "versionHistory",
|
||||
"args": [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 20
|
||||
}
|
||||
],
|
||||
"concreteType": "PolicyVersionConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "versions",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PolicyVersionEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PolicyVersion",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
(v11/*: any*/),
|
||||
(v12/*: any*/),
|
||||
(v13/*: any*/),
|
||||
(v14/*: any*/),
|
||||
(v15/*: any*/),
|
||||
(v6/*: any*/),
|
||||
(v18/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": "versions(first:20)"
|
||||
},
|
||||
{
|
||||
"alias": "latestVersion",
|
||||
"args": (v10/*: any*/),
|
||||
"concreteType": "PolicyVersionConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "versions",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PolicyVersionEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PolicyVersion",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
(v11/*: any*/),
|
||||
(v12/*: any*/),
|
||||
(v13/*: any*/),
|
||||
(v14/*: any*/),
|
||||
(v15/*: any*/),
|
||||
(v18/*: any*/),
|
||||
(v5/*: any*/),
|
||||
(v6/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": "versions(first:1)"
|
||||
}
|
||||
],
|
||||
"type": "Policy",
|
||||
"abstractKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "751e6cc23218ca2da7b5bf44d3eef59c",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "ShowPolicyViewQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query ShowPolicyViewQuery(\n $policyId: ID!\n) {\n node(id: $policyId) {\n __typename\n id\n ... on Policy {\n title\n description\n createdAt\n updatedAt\n currentPublishedVersion\n owner {\n id\n fullName\n primaryEmailAddress\n }\n ...SignaturesModal_policyVersions\n ...VersionHistoryModal_policyVersions\n latestVersion: versions(first: 1) {\n edges {\n node {\n id\n version\n status\n content\n changelog\n publishedAt\n publishedBy {\n fullName\n id\n }\n createdAt\n updatedAt\n }\n }\n }\n }\n }\n}\n\nfragment SignaturesModal_policyVersions on Policy {\n title\n policyVersions: versions(first: 10) {\n edges {\n node {\n id\n version\n status\n publishedAt\n updatedAt\n publishedBy {\n fullName\n id\n }\n signatures(first: 100) {\n edges {\n node {\n id\n state\n signedAt\n requestedAt\n signedBy {\n fullName\n id\n }\n requestedBy {\n fullName\n id\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n }\n}\n\nfragment VersionHistoryModal_policyVersions on Policy {\n title\n owner {\n fullName\n id\n }\n versionHistory: versions(first: 20) {\n edges {\n node {\n id\n version\n status\n content\n changelog\n publishedAt\n updatedAt\n publishedBy {\n fullName\n id\n }\n }\n }\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "58adb8690071648aecb252b2f807ea7d";
|
||||
|
||||
export default node;
|
||||
206
apps/console/src/pages/organizations/policies/__generated__/SignaturesModalOrganizationQuery.graphql.ts
generated
Normal file
206
apps/console/src/pages/organizations/policies/__generated__/SignaturesModalOrganizationQuery.graphql.ts
generated
Normal file
@@ -0,0 +1,206 @@
|
||||
/**
|
||||
* @generated SignedSource<<070580dfc3d76a6f1ca8b0227dc8c1c6>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type PeopleKind = "CONTRACTOR" | "EMPLOYEE" | "SERVICE_ACCOUNT";
|
||||
export type SignaturesModalOrganizationQuery$variables = {
|
||||
organizationId: string;
|
||||
};
|
||||
export type SignaturesModalOrganizationQuery$data = {
|
||||
readonly organization: {
|
||||
readonly id?: string;
|
||||
readonly peoples?: {
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly fullName: string;
|
||||
readonly id: string;
|
||||
readonly kind: PeopleKind;
|
||||
readonly primaryEmailAddress: string;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
};
|
||||
};
|
||||
export type SignaturesModalOrganizationQuery = {
|
||||
response: SignaturesModalOrganizationQuery$data;
|
||||
variables: SignaturesModalOrganizationQuery$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = [
|
||||
{
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "organizationId"
|
||||
}
|
||||
],
|
||||
v1 = [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "id",
|
||||
"variableName": "organizationId"
|
||||
}
|
||||
],
|
||||
v2 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
v3 = {
|
||||
"alias": null,
|
||||
"args": [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 100
|
||||
},
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "orderBy",
|
||||
"value": {
|
||||
"direction": "ASC",
|
||||
"field": "FULL_NAME"
|
||||
}
|
||||
}
|
||||
],
|
||||
"concreteType": "PeopleConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "peoples",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PeopleEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "People",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "fullName",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "primaryEmailAddress",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "kind",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": "peoples(first:100,orderBy:{\"direction\":\"ASC\",\"field\":\"FULL_NAME\"})"
|
||||
};
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "SignaturesModalOrganizationQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": "organization",
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"kind": "InlineFragment",
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
(v3/*: any*/)
|
||||
],
|
||||
"type": "Organization",
|
||||
"abstractKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Query",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "SignaturesModalOrganizationQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": "organization",
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__typename",
|
||||
"storageKey": null
|
||||
},
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"kind": "InlineFragment",
|
||||
"selections": [
|
||||
(v3/*: any*/)
|
||||
],
|
||||
"type": "Organization",
|
||||
"abstractKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "a2551c513c1c12a46c0da5328086475a",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "SignaturesModalOrganizationQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query SignaturesModalOrganizationQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n id\n peoples(first: 100, orderBy: {direction: ASC, field: FULL_NAME}) {\n edges {\n node {\n id\n fullName\n primaryEmailAddress\n kind\n }\n }\n }\n }\n id\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "39bec552bdf8762487cec72839225b84";
|
||||
|
||||
export default node;
|
||||
@@ -0,0 +1,264 @@
|
||||
/**
|
||||
* @generated SignedSource<<fda822cf46d8b9a49852a53edd8e363d>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type PolicyVersionSignatureState = "REQUESTED" | "SIGNED";
|
||||
export type RequestSignatureInput = {
|
||||
policyVersionId: string;
|
||||
signatoryId: string;
|
||||
};
|
||||
export type SignaturesModalRequestSignatureMutation$variables = {
|
||||
connections: ReadonlyArray<string>;
|
||||
input: RequestSignatureInput;
|
||||
};
|
||||
export type SignaturesModalRequestSignatureMutation$data = {
|
||||
readonly requestSignature: {
|
||||
readonly policyVersionSignatureEdge: {
|
||||
readonly node: {
|
||||
readonly id: string;
|
||||
readonly requestedAt: string;
|
||||
readonly requestedBy: {
|
||||
readonly fullName: string;
|
||||
};
|
||||
readonly signedAt: string | null | undefined;
|
||||
readonly signedBy: {
|
||||
readonly fullName: string;
|
||||
readonly id: string;
|
||||
};
|
||||
readonly state: PolicyVersionSignatureState;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
export type SignaturesModalRequestSignatureMutation = {
|
||||
response: SignaturesModalRequestSignatureMutation$data;
|
||||
variables: SignaturesModalRequestSignatureMutation$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = {
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "connections"
|
||||
},
|
||||
v1 = {
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "input"
|
||||
},
|
||||
v2 = [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "input",
|
||||
"variableName": "input"
|
||||
}
|
||||
],
|
||||
v3 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
v4 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "state",
|
||||
"storageKey": null
|
||||
},
|
||||
v5 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "signedAt",
|
||||
"storageKey": null
|
||||
},
|
||||
v6 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "requestedAt",
|
||||
"storageKey": null
|
||||
},
|
||||
v7 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "fullName",
|
||||
"storageKey": null
|
||||
},
|
||||
v8 = [
|
||||
(v7/*: any*/),
|
||||
(v3/*: any*/)
|
||||
],
|
||||
v9 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "People",
|
||||
"kind": "LinkedField",
|
||||
"name": "signedBy",
|
||||
"plural": false,
|
||||
"selections": (v8/*: any*/),
|
||||
"storageKey": null
|
||||
};
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": [
|
||||
(v0/*: any*/),
|
||||
(v1/*: any*/)
|
||||
],
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "SignaturesModalRequestSignatureMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "RequestSignaturePayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "requestSignature",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PolicyVersionSignatureEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "policyVersionSignatureEdge",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PolicyVersionSignature",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
(v4/*: any*/),
|
||||
(v5/*: any*/),
|
||||
(v6/*: any*/),
|
||||
(v9/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "People",
|
||||
"kind": "LinkedField",
|
||||
"name": "requestedBy",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v7/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": [
|
||||
(v1/*: any*/),
|
||||
(v0/*: any*/)
|
||||
],
|
||||
"kind": "Operation",
|
||||
"name": "SignaturesModalRequestSignatureMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "RequestSignaturePayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "requestSignature",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PolicyVersionSignatureEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "policyVersionSignatureEdge",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PolicyVersionSignature",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
(v4/*: any*/),
|
||||
(v5/*: any*/),
|
||||
(v6/*: any*/),
|
||||
(v9/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "People",
|
||||
"kind": "LinkedField",
|
||||
"name": "requestedBy",
|
||||
"plural": false,
|
||||
"selections": (v8/*: any*/),
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"filters": null,
|
||||
"handle": "prependEdge",
|
||||
"key": "",
|
||||
"kind": "LinkedHandle",
|
||||
"name": "policyVersionSignatureEdge",
|
||||
"handleArgs": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "connections",
|
||||
"variableName": "connections"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "4c6a5b6ed95c76c157f8627409a4481b",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "SignaturesModalRequestSignatureMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation SignaturesModalRequestSignatureMutation(\n $input: RequestSignatureInput!\n) {\n requestSignature(input: $input) {\n policyVersionSignatureEdge {\n node {\n id\n state\n signedAt\n requestedAt\n signedBy {\n fullName\n id\n }\n requestedBy {\n fullName\n id\n }\n }\n }\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "2b17fe4feae2614a6697c63a7334a1cf";
|
||||
|
||||
export default node;
|
||||
298
apps/console/src/pages/organizations/policies/__generated__/SignaturesModal_policyVersions.graphql.ts
generated
Normal file
298
apps/console/src/pages/organizations/policies/__generated__/SignaturesModal_policyVersions.graphql.ts
generated
Normal file
@@ -0,0 +1,298 @@
|
||||
/**
|
||||
* @generated SignedSource<<ee81b03b50b0bc65932295b898b856d4>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ReaderFragment } from 'relay-runtime';
|
||||
export type PolicyStatus = "DRAFT" | "PUBLISHED";
|
||||
export type PolicyVersionSignatureState = "REQUESTED" | "SIGNED";
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type SignaturesModal_policyVersions$data = {
|
||||
readonly policyVersions: {
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly id: string;
|
||||
readonly publishedAt: string | null | undefined;
|
||||
readonly publishedBy: {
|
||||
readonly fullName: string;
|
||||
} | null | undefined;
|
||||
readonly signatures: {
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly id: string;
|
||||
readonly requestedAt: string;
|
||||
readonly requestedBy: {
|
||||
readonly fullName: string;
|
||||
};
|
||||
readonly signedAt: string | null | undefined;
|
||||
readonly signedBy: {
|
||||
readonly fullName: string;
|
||||
readonly id: string;
|
||||
};
|
||||
readonly state: PolicyVersionSignatureState;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
readonly status: PolicyStatus;
|
||||
readonly updatedAt: string;
|
||||
readonly version: number;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
readonly title: string;
|
||||
readonly " $fragmentType": "SignaturesModal_policyVersions";
|
||||
};
|
||||
export type SignaturesModal_policyVersions$key = {
|
||||
readonly " $data"?: SignaturesModal_policyVersions$data;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"SignaturesModal_policyVersions">;
|
||||
};
|
||||
|
||||
const node: ReaderFragment = (function(){
|
||||
var v0 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
v1 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "fullName",
|
||||
"storageKey": null
|
||||
},
|
||||
v2 = [
|
||||
(v1/*: any*/)
|
||||
];
|
||||
return {
|
||||
"argumentDefinitions": [],
|
||||
"kind": "Fragment",
|
||||
"metadata": {
|
||||
"connection": [
|
||||
{
|
||||
"count": null,
|
||||
"cursor": null,
|
||||
"direction": "forward",
|
||||
"path": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "SignaturesModal_policyVersions",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "title",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": "policyVersions",
|
||||
"args": [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 10
|
||||
}
|
||||
],
|
||||
"concreteType": "PolicyVersionConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "versions",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PolicyVersionEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PolicyVersion",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v0/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "version",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "status",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "publishedAt",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "updatedAt",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "People",
|
||||
"kind": "LinkedField",
|
||||
"name": "publishedBy",
|
||||
"plural": false,
|
||||
"selections": (v2/*: any*/),
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": "signatures",
|
||||
"args": null,
|
||||
"concreteType": "PolicyVersionSignatureConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "__SignaturesModal_policyVersions_signatures_connection",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PolicyVersionSignatureEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PolicyVersionSignature",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v0/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "state",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "signedAt",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "requestedAt",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "People",
|
||||
"kind": "LinkedField",
|
||||
"name": "signedBy",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v1/*: any*/),
|
||||
(v0/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "People",
|
||||
"kind": "LinkedField",
|
||||
"name": "requestedBy",
|
||||
"plural": false,
|
||||
"selections": (v2/*: any*/),
|
||||
"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
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": "versions(first:10)"
|
||||
}
|
||||
],
|
||||
"type": "Policy",
|
||||
"abstractKey": null
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "da9bf0fc12618afb0f9ec8f2337b9b9b";
|
||||
|
||||
export default node;
|
||||
@@ -0,0 +1,181 @@
|
||||
/**
|
||||
* @generated SignedSource<<21626cad90fcb579071d73bc47e3fb7a>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ReaderFragment } from 'relay-runtime';
|
||||
export type PolicyStatus = "DRAFT" | "PUBLISHED";
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type VersionHistoryModal_policyVersions$data = {
|
||||
readonly owner: {
|
||||
readonly fullName: string;
|
||||
};
|
||||
readonly title: string;
|
||||
readonly versionHistory: {
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly changelog: string;
|
||||
readonly content: string;
|
||||
readonly id: string;
|
||||
readonly publishedAt: string | null | undefined;
|
||||
readonly publishedBy: {
|
||||
readonly fullName: string;
|
||||
} | null | undefined;
|
||||
readonly status: PolicyStatus;
|
||||
readonly updatedAt: string;
|
||||
readonly version: number;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
readonly " $fragmentType": "VersionHistoryModal_policyVersions";
|
||||
};
|
||||
export type VersionHistoryModal_policyVersions$key = {
|
||||
readonly " $data"?: VersionHistoryModal_policyVersions$data;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"VersionHistoryModal_policyVersions">;
|
||||
};
|
||||
|
||||
const node: ReaderFragment = (function(){
|
||||
var v0 = [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "fullName",
|
||||
"storageKey": null
|
||||
}
|
||||
];
|
||||
return {
|
||||
"argumentDefinitions": [],
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "VersionHistoryModal_policyVersions",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "title",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "People",
|
||||
"kind": "LinkedField",
|
||||
"name": "owner",
|
||||
"plural": false,
|
||||
"selections": (v0/*: any*/),
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": "versionHistory",
|
||||
"args": [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 20
|
||||
}
|
||||
],
|
||||
"concreteType": "PolicyVersionConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "versions",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PolicyVersionEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PolicyVersion",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "version",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "status",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "content",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "changelog",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "publishedAt",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "updatedAt",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "People",
|
||||
"kind": "LinkedField",
|
||||
"name": "publishedBy",
|
||||
"plural": false,
|
||||
"selections": (v0/*: any*/),
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": "versions(first:20)"
|
||||
}
|
||||
],
|
||||
"type": "Policy",
|
||||
"abstractKey": null
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "09cd9142880cfbb239ce866ea81afe48";
|
||||
|
||||
export default node;
|
||||
@@ -101,8 +101,7 @@ const showRiskViewQuery = graphql`
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
status
|
||||
title
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
@@ -155,8 +154,7 @@ const organizationPoliciesQuery = graphql`
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
status
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -530,7 +528,7 @@ function ShowRiskViewContent({
|
||||
return policies.filter((policy) => {
|
||||
return (
|
||||
!policySearchQuery ||
|
||||
policy.name.toLowerCase().includes(policySearchQuery.toLowerCase())
|
||||
policy.title.toLowerCase().includes(policySearchQuery.toLowerCase())
|
||||
);
|
||||
});
|
||||
}, [getPolicies, policySearchQuery]);
|
||||
@@ -731,7 +729,7 @@ function ShowRiskViewContent({
|
||||
|
||||
toast({
|
||||
title: "Success",
|
||||
description: `Linked policy "${policy.name}" to this risk.`,
|
||||
description: `Linked policy "${policy.title}" to this risk.`,
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
@@ -803,7 +801,7 @@ function ShowRiskViewContent({
|
||||
|
||||
toast({
|
||||
title: "Success",
|
||||
description: `Unlinked policy "${policy.name}" from this risk.`,
|
||||
description: `Unlinked policy "${policy.title}" from this risk.`,
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
@@ -1090,15 +1088,26 @@ function ShowRiskViewContent({
|
||||
<TabsContent value="policies" className="space-y-4">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h2 className="text-xl font-semibold">Risk Policies</h2>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setIsPolicyDialogOpen(true);
|
||||
loadPoliciesData();
|
||||
}}
|
||||
>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Link Policy
|
||||
</Button>
|
||||
<div className="flex space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
asChild
|
||||
>
|
||||
<Link to={`/organizations/${organizationId}/policies/new?riskId=${risk.id}`}>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
New Version
|
||||
</Link>
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setIsPolicyDialogOpen(true);
|
||||
loadPoliciesData();
|
||||
}}
|
||||
>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Link Policy
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{policies.length > 0 ? (
|
||||
<div className="rounded-md border">
|
||||
@@ -1117,7 +1126,7 @@ function ShowRiskViewContent({
|
||||
to={`/organizations/${organizationId}/policies/${policy.id}`}
|
||||
className="font-medium text-blue-600 hover:underline"
|
||||
>
|
||||
{policy.name}
|
||||
{policy.title}
|
||||
</Link>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
@@ -1350,7 +1359,7 @@ function ShowRiskViewContent({
|
||||
>
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="font-medium">{policy.name}</h3>
|
||||
<h3 className="font-medium">{policy.title}</h3>
|
||||
</div>
|
||||
{isLinked ? (
|
||||
<Button
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<86be887d4ec59050fd69900fd80863a2>>
|
||||
* @generated SignedSource<<0335bb26d78f02170c02c7c2279a6bfd>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -9,7 +9,6 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type PolicyStatus = "ACTIVE" | "DRAFT";
|
||||
export type ShowRiskViewOrganizationPoliciesQuery$variables = {
|
||||
organizationId: string;
|
||||
};
|
||||
@@ -20,8 +19,7 @@ export type ShowRiskViewOrganizationPoliciesQuery$data = {
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly status: PolicyStatus;
|
||||
readonly title: string;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
@@ -83,14 +81,7 @@ v4 = [
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "name",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "status",
|
||||
"name": "title",
|
||||
"storageKey": null
|
||||
},
|
||||
(v3/*: any*/)
|
||||
@@ -228,7 +219,7 @@ return {
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "a229f1f805e79f8de378305e021d7fea",
|
||||
"cacheID": "a92a6b2390e95e67b42fd18cc1030a9a",
|
||||
"id": null,
|
||||
"metadata": {
|
||||
"connection": [
|
||||
@@ -245,11 +236,11 @@ return {
|
||||
},
|
||||
"name": "ShowRiskViewOrganizationPoliciesQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query ShowRiskViewOrganizationPoliciesQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ... on Organization {\n policies(first: 100) {\n edges {\n node {\n id\n name\n status\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
|
||||
"text": "query ShowRiskViewOrganizationPoliciesQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ... on Organization {\n policies(first: 100) {\n edges {\n node {\n id\n title\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "26cbf19cd716aee7413931357b9c5354";
|
||||
(node as any).hash = "b47adf6c3a247367a6b23dd161df2702";
|
||||
|
||||
export default node;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<e318c9e0fe4355eb4923acb54da59ebb>>
|
||||
* @generated SignedSource<<5d1745636bcff5f05d946bcd613463e5>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type MesureState = "IMPLEMENTED" | "IN_PROGRESS" | "NOT_APPLICABLE" | "NOT_STARTED";
|
||||
export type PolicyStatus = "ACTIVE" | "DRAFT";
|
||||
export type RiskTreatment = "ACCEPTED" | "AVOIDED" | "MITIGATED" | "TRANSFERRED";
|
||||
export type ShowRiskViewQuery$variables = {
|
||||
riskId: string;
|
||||
@@ -56,8 +55,7 @@ export type ShowRiskViewQuery$data = {
|
||||
readonly node: {
|
||||
readonly createdAt: string;
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly status: PolicyStatus;
|
||||
readonly title: string;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
@@ -285,12 +283,11 @@ v18 = [
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "status",
|
||||
"name": "title",
|
||||
"storageKey": null
|
||||
},
|
||||
(v12/*: any*/),
|
||||
@@ -517,7 +514,7 @@ return {
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "05f0fe48c916ecda883f606eed0c9244",
|
||||
"cacheID": "a88eba4c398c292d41deaddd1d77bb3b",
|
||||
"id": null,
|
||||
"metadata": {
|
||||
"connection": [
|
||||
@@ -552,11 +549,11 @@ return {
|
||||
},
|
||||
"name": "ShowRiskViewQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query ShowRiskViewQuery(\n $riskId: ID!\n) {\n node(id: $riskId) {\n __typename\n id\n ... on Risk {\n name\n description\n treatment\n owner {\n id\n fullName\n }\n inherentLikelihood\n inherentImpact\n residualLikelihood\n residualImpact\n note\n createdAt\n updatedAt\n mesures(first: 100) {\n edges {\n node {\n id\n name\n description\n category\n createdAt\n state\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n policies(first: 100) {\n edges {\n node {\n id\n name\n status\n createdAt\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n controls(first: 100) {\n edges {\n node {\n id\n referenceId\n name\n description\n createdAt\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
|
||||
"text": "query ShowRiskViewQuery(\n $riskId: ID!\n) {\n node(id: $riskId) {\n __typename\n id\n ... on Risk {\n name\n description\n treatment\n owner {\n id\n fullName\n }\n inherentLikelihood\n inherentImpact\n residualLikelihood\n residualImpact\n note\n createdAt\n updatedAt\n mesures(first: 100) {\n edges {\n node {\n id\n name\n description\n category\n createdAt\n state\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n policies(first: 100) {\n edges {\n node {\n id\n title\n createdAt\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n controls(first: 100) {\n edges {\n node {\n id\n referenceId\n name\n description\n createdAt\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "25e67618b61999a955716e937a53a470";
|
||||
(node as any).hash = "96158658ecb7c406fc969e086917c03e";
|
||||
|
||||
export default node;
|
||||
|
||||
@@ -31,4 +31,6 @@ const (
|
||||
EmailEntityType
|
||||
ControlEntityType
|
||||
RiskEntityType
|
||||
PolicyVersionEntityType
|
||||
PolicyVersionSignatureEntityType
|
||||
)
|
||||
|
||||
60
pkg/coredata/migrations/20250424T094500Z.sql
Normal file
60
pkg/coredata/migrations/20250424T094500Z.sql
Normal file
@@ -0,0 +1,60 @@
|
||||
CREATE TABLE policy_versions (
|
||||
id TEXT PRIMARY KEY,
|
||||
tenant_id TEXT NOT NULL,
|
||||
policy_id TEXT NOT NULL REFERENCES policies(id) ON DELETE CASCADE,
|
||||
version_number INTEGER NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
changelog TEXT NOT NULL,
|
||||
created_by TEXT NOT NULL,
|
||||
status policy_status NOT NULL,
|
||||
published_by TEXT REFERENCES peoples(id),
|
||||
published_at TIMESTAMP WITH TIME ZONE,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
UNIQUE(policy_id, version_number)
|
||||
);
|
||||
|
||||
INSERT INTO policy_versions (
|
||||
id,
|
||||
tenant_id,
|
||||
policy_id,
|
||||
version_number,
|
||||
content,
|
||||
changelog,
|
||||
created_by,
|
||||
status,
|
||||
published_by,
|
||||
published_at,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
SELECT
|
||||
generate_gid(decode_base64_unpadded(p.tenant_id), 16),
|
||||
p.tenant_id,
|
||||
p.id,
|
||||
1,
|
||||
'',
|
||||
'Initial version',
|
||||
p.owner_id,
|
||||
'DRAFT',
|
||||
NULL,
|
||||
NULL,
|
||||
p.created_at,
|
||||
p.updated_at
|
||||
FROM policies p;
|
||||
|
||||
ALTER TABLE policies RENAME COLUMN name TO title;
|
||||
ALTER TABLE policies DROP COLUMN content;
|
||||
ALTER TABLE policies ADD COLUMN description TEXT NOT NULL DEFAULT '';
|
||||
ALTER TABLE policies ALTER COLUMN owner_id DROP NOT NULL;
|
||||
ALTER TABLE policies DROP COLUMN review_date;
|
||||
ALTER TABLE policies DROP COLUMN status;
|
||||
ALTER TABLE policies ADD COLUMN current_published_version INTEGER;
|
||||
|
||||
UPDATE policies p
|
||||
SET current_published_version = 1
|
||||
WHERE EXISTS (
|
||||
SELECT 1 FROM policy_versions pv
|
||||
WHERE pv.policy_id = p.id
|
||||
AND pv.status = 'published'
|
||||
);
|
||||
22
pkg/coredata/migrations/20250425T012700Z.sql
Normal file
22
pkg/coredata/migrations/20250425T012700Z.sql
Normal file
@@ -0,0 +1,22 @@
|
||||
CREATE TYPE policy_status_new AS ENUM ('DRAFT', 'PUBLISHED');
|
||||
|
||||
ALTER TABLE policy_versions ADD COLUMN temp_status TEXT;
|
||||
|
||||
UPDATE policy_versions SET temp_status =
|
||||
CASE WHEN status::TEXT = 'ACTIVE' THEN 'PUBLISHED'
|
||||
ELSE status::TEXT
|
||||
END;
|
||||
|
||||
ALTER TABLE policy_versions DROP COLUMN status;
|
||||
|
||||
ALTER TABLE policy_versions ADD COLUMN status policy_status_new;
|
||||
|
||||
UPDATE policy_versions SET status = temp_status::policy_status_new;
|
||||
|
||||
ALTER TABLE policy_versions DROP COLUMN temp_status;
|
||||
|
||||
DROP TYPE policy_status;
|
||||
ALTER TYPE policy_status_new RENAME TO policy_status;
|
||||
|
||||
CREATE UNIQUE INDEX policy_one_draft_version_idx ON policy_versions (policy_id, status)
|
||||
WHERE status = 'DRAFT';
|
||||
15
pkg/coredata/migrations/20250428T024800Z.sql
Normal file
15
pkg/coredata/migrations/20250428T024800Z.sql
Normal file
@@ -0,0 +1,15 @@
|
||||
CREATE TYPE policy_version_signature_state AS ENUM ('REQUESTED', 'SIGNED');
|
||||
|
||||
CREATE TABLE policy_version_signatures (
|
||||
id TEXT PRIMARY KEY,
|
||||
tenant_id TEXT NOT NULL,
|
||||
policy_version_id TEXT NOT NULL REFERENCES policy_versions(id) ON DELETE CASCADE,
|
||||
state policy_version_signature_state NOT NULL,
|
||||
signed_by TEXT NOT NULL REFERENCES peoples(id),
|
||||
signed_at TIMESTAMP WITH TIME ZONE,
|
||||
requested_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
requested_by TEXT NOT NULL REFERENCES peoples(id),
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
UNIQUE (policy_version_id, signed_by)
|
||||
);
|
||||
@@ -102,7 +102,6 @@ func (p *People) LoadByUserID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
organizationID gid.GID,
|
||||
userID gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
@@ -120,14 +119,13 @@ FROM
|
||||
peoples
|
||||
WHERE
|
||||
%s
|
||||
AND organization_id = @organization_id
|
||||
AND user_id = @user_id
|
||||
LIMIT 1;
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"organization_id": organizationID, "user_id": userID}
|
||||
args := pgx.StrictNamedArgs{"user_id": userID}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package coredata
|
||||
|
||||
import (
|
||||
@@ -14,15 +28,13 @@ import (
|
||||
|
||||
type (
|
||||
Policy struct {
|
||||
ID gid.GID `db:"id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
OwnerID gid.GID `db:"owner_id"`
|
||||
Status PolicyStatus `db:"status"`
|
||||
Name string `db:"name"`
|
||||
Content string `db:"content"`
|
||||
ReviewDate *time.Time `db:"review_date"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
ID gid.GID `db:"id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
OwnerID gid.GID `db:"owner_id"`
|
||||
Title string `db:"title"`
|
||||
CurrentPublishedVersion *int `db:"current_published_version"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
Policies []*Policy
|
||||
@@ -32,8 +44,8 @@ func (p Policy) CursorKey(orderBy PolicyOrderField) page.CursorKey {
|
||||
switch orderBy {
|
||||
case PolicyOrderFieldCreatedAt:
|
||||
return page.NewCursorKey(p.ID, p.CreatedAt)
|
||||
case PolicyOrderFieldName:
|
||||
return page.NewCursorKey(p.ID, p.Name)
|
||||
case PolicyOrderFieldTitle:
|
||||
return page.NewCursorKey(p.ID, p.Title)
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("unsupported order by: %s", orderBy))
|
||||
@@ -50,10 +62,8 @@ SELECT
|
||||
id,
|
||||
organization_id,
|
||||
owner_id,
|
||||
name,
|
||||
status,
|
||||
content,
|
||||
review_date,
|
||||
title,
|
||||
current_published_version,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
@@ -93,13 +103,11 @@ func (p *Policies) LoadByOrganizationID(
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
id,
|
||||
organization_id,
|
||||
owner_id,
|
||||
name,
|
||||
status,
|
||||
content,
|
||||
review_date,
|
||||
title,
|
||||
current_published_version,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
@@ -140,41 +148,35 @@ func (p Policy) Insert(
|
||||
INSERT INTO
|
||||
policies (
|
||||
tenant_id,
|
||||
id,
|
||||
organization_id,
|
||||
owner_id,
|
||||
name,
|
||||
status,
|
||||
content,
|
||||
review_date,
|
||||
created_at,
|
||||
updated_at
|
||||
id,
|
||||
organization_id,
|
||||
owner_id,
|
||||
title,
|
||||
current_published_version,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
VALUES (
|
||||
@tenant_id,
|
||||
@policy_id,
|
||||
@organization_id,
|
||||
@owner_id,
|
||||
@name,
|
||||
@status,
|
||||
@content,
|
||||
@review_date,
|
||||
@title,
|
||||
@current_published_version,
|
||||
@created_at,
|
||||
@updated_at
|
||||
);
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"policy_id": p.ID,
|
||||
"organization_id": p.OrganizationID,
|
||||
"owner_id": p.OwnerID,
|
||||
"name": p.Name,
|
||||
"status": p.Status,
|
||||
"content": p.Content,
|
||||
"review_date": p.ReviewDate,
|
||||
"created_at": p.CreatedAt,
|
||||
"updated_at": p.UpdatedAt,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"policy_id": p.ID,
|
||||
"organization_id": p.OrganizationID,
|
||||
"owner_id": p.OwnerID,
|
||||
"title": p.Title,
|
||||
"current_published_version": p.CurrentPublishedVersion,
|
||||
"created_at": p.CreatedAt,
|
||||
"updated_at": p.UpdatedAt,
|
||||
}
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
return err
|
||||
@@ -207,10 +209,8 @@ func (p *Policy) Update(
|
||||
UPDATE
|
||||
policies
|
||||
SET
|
||||
name = @name,
|
||||
status = @status,
|
||||
content = @content,
|
||||
review_date = @review_date,
|
||||
title = @title,
|
||||
current_published_version = @current_published_version,
|
||||
owner_id = @owner_id,
|
||||
updated_at = @updated_at
|
||||
WHERE %s
|
||||
@@ -219,13 +219,11 @@ WHERE %s
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"policy_id": p.ID,
|
||||
"updated_at": time.Now(),
|
||||
"name": p.Name,
|
||||
"content": p.Content,
|
||||
"status": p.Status,
|
||||
"review_date": p.ReviewDate,
|
||||
"owner_id": p.OwnerID,
|
||||
"policy_id": p.ID,
|
||||
"updated_at": time.Now(),
|
||||
"title": p.Title,
|
||||
"current_published_version": p.CurrentPublishedVersion,
|
||||
"owner_id": p.OwnerID,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
@@ -251,10 +249,8 @@ WITH plcs AS (
|
||||
p.tenant_id,
|
||||
p.organization_id,
|
||||
p.owner_id,
|
||||
p.name,
|
||||
p.content,
|
||||
p.status,
|
||||
p.review_date,
|
||||
p.title,
|
||||
p.current_published_version,
|
||||
p.created_at,
|
||||
p.updated_at
|
||||
FROM
|
||||
@@ -268,10 +264,8 @@ SELECT
|
||||
id,
|
||||
organization_id,
|
||||
owner_id,
|
||||
name,
|
||||
content,
|
||||
status,
|
||||
review_date,
|
||||
title,
|
||||
current_published_version,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
@@ -314,10 +308,8 @@ WITH plcs AS (
|
||||
p.tenant_id,
|
||||
p.organization_id,
|
||||
p.owner_id,
|
||||
p.name,
|
||||
p.content,
|
||||
p.status,
|
||||
p.review_date,
|
||||
p.title,
|
||||
p.current_published_version,
|
||||
p.created_at,
|
||||
p.updated_at
|
||||
FROM
|
||||
@@ -331,10 +323,8 @@ SELECT
|
||||
id,
|
||||
organization_id,
|
||||
owner_id,
|
||||
name,
|
||||
content,
|
||||
status,
|
||||
review_date,
|
||||
title,
|
||||
current_published_version,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
|
||||
@@ -20,7 +20,7 @@ type (
|
||||
|
||||
const (
|
||||
PolicyOrderFieldCreatedAt PolicyOrderField = "CREATED_AT"
|
||||
PolicyOrderFieldName PolicyOrderField = "NAME"
|
||||
PolicyOrderFieldTitle PolicyOrderField = "TITLE"
|
||||
)
|
||||
|
||||
func (p PolicyOrderField) Column() string {
|
||||
|
||||
@@ -25,7 +25,7 @@ type (
|
||||
|
||||
const (
|
||||
PolicyStatusDraft PolicyStatus = iota
|
||||
PolicyStatusActive
|
||||
PolicyStatusPublished
|
||||
)
|
||||
|
||||
func (ps PolicyStatus) MarshalText() ([]byte, error) {
|
||||
@@ -38,8 +38,8 @@ func (ps *PolicyStatus) UnmarshalText(data []byte) error {
|
||||
switch val {
|
||||
case PolicyStatusDraft.String():
|
||||
*ps = PolicyStatusDraft
|
||||
case PolicyStatusActive.String():
|
||||
*ps = PolicyStatusActive
|
||||
case PolicyStatusPublished.String():
|
||||
*ps = PolicyStatusPublished
|
||||
default:
|
||||
return fmt.Errorf("invalid PolicyStatus value: %q", val)
|
||||
}
|
||||
@@ -53,8 +53,8 @@ func (ps PolicyStatus) String() string {
|
||||
switch ps {
|
||||
case PolicyStatusDraft:
|
||||
val = "DRAFT"
|
||||
case PolicyStatusActive:
|
||||
val = "ACTIVE"
|
||||
case PolicyStatusPublished:
|
||||
val = "PUBLISHED"
|
||||
}
|
||||
|
||||
return val
|
||||
|
||||
344
pkg/coredata/policy_version.go
Normal file
344
pkg/coredata/policy_version.go
Normal file
@@ -0,0 +1,344 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package coredata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"github.com/getprobo/probo/pkg/gid"
|
||||
"github.com/getprobo/probo/pkg/page"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"go.gearno.de/kit/pg"
|
||||
)
|
||||
|
||||
type (
|
||||
PolicyVersion struct {
|
||||
ID gid.GID `db:"id"`
|
||||
PolicyID gid.GID `db:"policy_id"`
|
||||
VersionNumber int `db:"version_number"`
|
||||
Content string `db:"content"`
|
||||
Changelog string `db:"changelog"`
|
||||
CreatedBy gid.GID `db:"created_by"`
|
||||
Status PolicyStatus `db:"status"`
|
||||
PublishedBy *gid.GID `db:"published_by"`
|
||||
PublishedAt *time.Time `db:"published_at"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
PolicyVersions []*PolicyVersion
|
||||
)
|
||||
|
||||
func (p *PolicyVersions) LoadByPolicyID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
policyID gid.GID,
|
||||
cursor *page.Cursor[PolicyVersionOrderField],
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
policy_id,
|
||||
version_number,
|
||||
content,
|
||||
changelog,
|
||||
created_by,
|
||||
status,
|
||||
published_by,
|
||||
published_at,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
policy_versions
|
||||
WHERE
|
||||
%s
|
||||
AND policy_id = @policy_id
|
||||
AND %s
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"policy_id": policyID}
|
||||
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 policy versions: %w", err)
|
||||
}
|
||||
|
||||
policyVersions, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[PolicyVersion])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect policy versions: %w", err)
|
||||
}
|
||||
|
||||
*p = policyVersions
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p PolicyVersion) CursorKey(orderBy PolicyVersionOrderField) page.CursorKey {
|
||||
switch orderBy {
|
||||
case PolicyVersionOrderFieldCreatedAt:
|
||||
return page.NewCursorKey(p.ID, p.CreatedAt)
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("unsupported order by: %s", orderBy))
|
||||
}
|
||||
|
||||
func (p *PolicyVersion) LoadByID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
policyVersionID gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
policy_id,
|
||||
version_number,
|
||||
content,
|
||||
changelog,
|
||||
created_by,
|
||||
status,
|
||||
published_by,
|
||||
published_at,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
policy_versions
|
||||
WHERE
|
||||
%s
|
||||
AND id = @policy_version_id
|
||||
LIMIT 1;
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"policy_version_id": policyVersionID}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query policy versions: %w", err)
|
||||
}
|
||||
|
||||
policyVersion, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[PolicyVersion])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect policy version: %w", err)
|
||||
}
|
||||
|
||||
*p = policyVersion
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p PolicyVersion) Insert(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
) error {
|
||||
q := `
|
||||
INSERT INTO policy_versions (
|
||||
tenant_id,
|
||||
id,
|
||||
policy_id,
|
||||
version_number,
|
||||
content,
|
||||
changelog,
|
||||
created_by,
|
||||
status,
|
||||
created_at,
|
||||
updated_at
|
||||
) VALUES (
|
||||
@tenant_id,
|
||||
@id,
|
||||
@policy_id,
|
||||
@version_number,
|
||||
@content,
|
||||
@changelog,
|
||||
@created_by,
|
||||
@status,
|
||||
@created_at,
|
||||
@updated_at
|
||||
)
|
||||
`
|
||||
|
||||
now := time.Now()
|
||||
args := pgx.StrictNamedArgs{
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"id": p.ID,
|
||||
"policy_id": p.PolicyID,
|
||||
"version_number": p.VersionNumber,
|
||||
"content": p.Content,
|
||||
"changelog": p.Changelog,
|
||||
"created_by": p.CreatedBy,
|
||||
"status": PolicyStatusDraft,
|
||||
"created_at": now,
|
||||
"updated_at": now,
|
||||
}
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating/updating policy version: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PolicyVersion) LoadByPolicyIDAndVersionNumber(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
policyID gid.GID,
|
||||
versionNumber int,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
policy_id,
|
||||
version_number,
|
||||
content,
|
||||
changelog,
|
||||
created_by,
|
||||
status,
|
||||
published_by,
|
||||
published_at,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
policy_versions
|
||||
WHERE
|
||||
%s
|
||||
AND policy_id = @policy_id
|
||||
AND version_number = @version_number
|
||||
LIMIT 1;
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"policy_id": policyID,
|
||||
"version_number": versionNumber,
|
||||
}
|
||||
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query policy versions: %w", err)
|
||||
}
|
||||
|
||||
policyVersion, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[PolicyVersion])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect policy version: %w", err)
|
||||
}
|
||||
|
||||
*p = policyVersion
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PolicyVersion) LoadLatestVersion(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
policyID gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
policy_id,
|
||||
version_number,
|
||||
content,
|
||||
changelog,
|
||||
created_by,
|
||||
status,
|
||||
published_by,
|
||||
published_at,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
policy_versions
|
||||
WHERE
|
||||
%s
|
||||
AND policy_id = @policy_id
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1;
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"policy_id": policyID,
|
||||
}
|
||||
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query policy versions: %w", err)
|
||||
}
|
||||
|
||||
policyVersion, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[PolicyVersion])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect policy version: %w", err)
|
||||
}
|
||||
|
||||
*p = policyVersion
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p PolicyVersion) Update(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
) error {
|
||||
q := `
|
||||
UPDATE policy_versions SET
|
||||
changelog = @changelog,
|
||||
status = @status,
|
||||
content = @content,
|
||||
published_by = @published_by,
|
||||
published_at = @published_at,
|
||||
updated_at = @updated_at
|
||||
WHERE %s
|
||||
AND id = @policy_version_id;`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"policy_version_id": p.ID,
|
||||
"changelog": p.Changelog,
|
||||
"status": p.Status,
|
||||
"content": p.Content,
|
||||
"published_by": p.PublishedBy,
|
||||
"published_at": p.PublishedAt,
|
||||
"updated_at": p.UpdatedAt,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot update policy version: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
41
pkg/coredata/policy_version_order_field.go
Normal file
41
pkg/coredata/policy_version_order_field.go
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package coredata
|
||||
|
||||
type (
|
||||
PolicyVersionOrderField string
|
||||
)
|
||||
|
||||
const (
|
||||
PolicyVersionOrderFieldCreatedAt PolicyVersionOrderField = "CREATED_AT"
|
||||
PolicyVersionOrderFieldVersion PolicyVersionOrderField = "VERSION"
|
||||
)
|
||||
|
||||
func (p PolicyVersionOrderField) Column() string {
|
||||
return string(p)
|
||||
}
|
||||
|
||||
func (p PolicyVersionOrderField) String() string {
|
||||
return string(p)
|
||||
}
|
||||
|
||||
func (p PolicyVersionOrderField) MarshalText() ([]byte, error) {
|
||||
return []byte(p.String()), nil
|
||||
}
|
||||
|
||||
func (p *PolicyVersionOrderField) UnmarshalText(text []byte) error {
|
||||
*p = PolicyVersionOrderField(text)
|
||||
return nil
|
||||
}
|
||||
197
pkg/coredata/policy_version_signature.go
Normal file
197
pkg/coredata/policy_version_signature.go
Normal file
@@ -0,0 +1,197 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package coredata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"github.com/getprobo/probo/pkg/gid"
|
||||
"github.com/getprobo/probo/pkg/page"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"go.gearno.de/kit/pg"
|
||||
)
|
||||
|
||||
type (
|
||||
PolicyVersionSignature struct {
|
||||
ID gid.GID `json:"id"`
|
||||
PolicyVersionID gid.GID `json:"policy_version_id"`
|
||||
State PolicyVersionSignatureState `json:"state"`
|
||||
SignedBy gid.GID `json:"signed_by"`
|
||||
SignedAt *time.Time `json:"signed_at"`
|
||||
RequestedAt time.Time `json:"requested_at"`
|
||||
RequestedBy gid.GID `json:"requested_by"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
PolicyVersionSignatures []*PolicyVersionSignature
|
||||
)
|
||||
|
||||
func (pvs PolicyVersionSignature) CursorKey(orderBy PolicyVersionSignatureOrderField) page.CursorKey {
|
||||
switch orderBy {
|
||||
case PolicyVersionSignatureOrderFieldCreatedAt:
|
||||
return page.NewCursorKey(pvs.ID, pvs.CreatedAt)
|
||||
case PolicyVersionSignatureOrderFieldSignedAt:
|
||||
return page.NewCursorKey(pvs.ID, pvs.SignedAt)
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("unsupported order by: %s", orderBy))
|
||||
}
|
||||
|
||||
func (pvs *PolicyVersionSignature) LoadByID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
signatureID gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
policy_version_id,
|
||||
state,
|
||||
signed_by,
|
||||
signed_at,
|
||||
requested_at,
|
||||
requested_by,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
policy_version_signatures
|
||||
WHERE
|
||||
id = @policy_version_signature_id
|
||||
AND %s
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"policy_version_signature_id": signatureID}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query policy version signature: %w", err)
|
||||
}
|
||||
|
||||
policyVersionSignature, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[PolicyVersionSignature])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect policy version signature: %w", err)
|
||||
}
|
||||
|
||||
*pvs = policyVersionSignature
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pvs PolicyVersionSignature) Insert(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
) error {
|
||||
q := `
|
||||
INSERT INTO policy_version_signatures (
|
||||
id,
|
||||
tenant_id,
|
||||
policy_version_id,
|
||||
state,
|
||||
signed_by,
|
||||
signed_at,
|
||||
requested_at,
|
||||
requested_by,
|
||||
created_at,
|
||||
updated_at
|
||||
) VALUES (
|
||||
@id,
|
||||
@tenant_id,
|
||||
@policy_version_id,
|
||||
@state,
|
||||
@signed_by,
|
||||
@signed_at,
|
||||
@requested_at,
|
||||
@requested_by,
|
||||
@created_at,
|
||||
@updated_at
|
||||
)
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"id": pvs.ID,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"policy_version_id": pvs.PolicyVersionID,
|
||||
"state": pvs.State,
|
||||
"signed_by": pvs.SignedBy,
|
||||
"signed_at": pvs.SignedAt,
|
||||
"requested_at": pvs.RequestedAt,
|
||||
"requested_by": pvs.RequestedBy,
|
||||
"created_at": pvs.CreatedAt,
|
||||
"updated_at": pvs.UpdatedAt,
|
||||
}
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot insert policy version signature: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pvss *PolicyVersionSignatures) LoadByPolicyVersionID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
policyVersionID gid.GID,
|
||||
cursor *page.Cursor[PolicyVersionSignatureOrderField],
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
policy_version_id,
|
||||
state,
|
||||
signed_by,
|
||||
signed_at,
|
||||
requested_at,
|
||||
requested_by,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
policy_version_signatures
|
||||
WHERE
|
||||
%s
|
||||
AND policy_version_id = @policy_version_id
|
||||
AND %s
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"policy_version_id": policyVersionID}
|
||||
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 policy version signatures: %w", err)
|
||||
}
|
||||
|
||||
policyVersionSignatures, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[PolicyVersionSignature])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect policy version signatures: %w", err)
|
||||
}
|
||||
|
||||
*pvss = policyVersionSignatures
|
||||
|
||||
return nil
|
||||
}
|
||||
41
pkg/coredata/policy_version_signature_order_field.go
Normal file
41
pkg/coredata/policy_version_signature_order_field.go
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package coredata
|
||||
|
||||
type (
|
||||
PolicyVersionSignatureOrderField string
|
||||
)
|
||||
|
||||
const (
|
||||
PolicyVersionSignatureOrderFieldCreatedAt PolicyVersionSignatureOrderField = "CREATED_AT"
|
||||
PolicyVersionSignatureOrderFieldSignedAt PolicyVersionSignatureOrderField = "SIGNED_AT"
|
||||
)
|
||||
|
||||
func (p PolicyVersionSignatureOrderField) Column() string {
|
||||
return string(p)
|
||||
}
|
||||
|
||||
func (p PolicyVersionSignatureOrderField) String() string {
|
||||
return string(p)
|
||||
}
|
||||
|
||||
func (p PolicyVersionSignatureOrderField) MarshalText() ([]byte, error) {
|
||||
return []byte(p.String()), nil
|
||||
}
|
||||
|
||||
func (p *PolicyVersionSignatureOrderField) UnmarshalText(text []byte) error {
|
||||
*p = PolicyVersionSignatureOrderField(text)
|
||||
return nil
|
||||
}
|
||||
74
pkg/coredata/policy_version_signature_state.go
Normal file
74
pkg/coredata/policy_version_signature_state.go
Normal file
@@ -0,0 +1,74 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package coredata
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type (
|
||||
PolicyVersionSignatureState string
|
||||
)
|
||||
|
||||
const (
|
||||
PolicyVersionSignatureStateRequested PolicyVersionSignatureState = "REQUESTED"
|
||||
PolicyVersionSignatureStateSigned PolicyVersionSignatureState = "SIGNED"
|
||||
)
|
||||
|
||||
func (pvs PolicyVersionSignatureState) MarshalText() ([]byte, error) {
|
||||
return []byte(pvs.String()), nil
|
||||
}
|
||||
|
||||
func (pvs *PolicyVersionSignatureState) UnmarshalText(data []byte) error {
|
||||
val := string(data)
|
||||
|
||||
switch val {
|
||||
case PolicyVersionSignatureStateRequested.String():
|
||||
*pvs = PolicyVersionSignatureStateRequested
|
||||
case PolicyVersionSignatureStateSigned.String():
|
||||
*pvs = PolicyVersionSignatureStateSigned
|
||||
default:
|
||||
return fmt.Errorf("invalid MesureState value: %q", val)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pvs PolicyVersionSignatureState) String() string {
|
||||
var val string
|
||||
|
||||
switch pvs {
|
||||
case PolicyVersionSignatureStateRequested:
|
||||
val = "REQUESTED"
|
||||
case PolicyVersionSignatureStateSigned:
|
||||
val = "SIGNED"
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
func (pvs *PolicyVersionSignatureState) Scan(value any) error {
|
||||
val, ok := value.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid scan source for MesureState, expected string got %T", value)
|
||||
}
|
||||
|
||||
return pvs.UnmarshalText([]byte(val))
|
||||
}
|
||||
|
||||
func (pvs PolicyVersionSignatureState) Value() (driver.Value, error) {
|
||||
return pvs.String(), nil
|
||||
}
|
||||
@@ -71,7 +71,6 @@ func (s PeopleService) Get(
|
||||
|
||||
func (s PeopleService) GetByUserID(
|
||||
ctx context.Context,
|
||||
organizationID gid.GID,
|
||||
userID gid.GID,
|
||||
) (*coredata.People, error) {
|
||||
people := &coredata.People{}
|
||||
@@ -79,7 +78,7 @@ func (s PeopleService) GetByUserID(
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
return people.LoadByUserID(ctx, conn, s.svc.scope, organizationID, userID)
|
||||
return people.LoadByUserID(ctx, conn, s.svc.scope, userID)
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -18,21 +18,21 @@ type PolicyService struct {
|
||||
type (
|
||||
CreatePolicyRequest struct {
|
||||
OrganizationID gid.GID
|
||||
Name string
|
||||
Status coredata.PolicyStatus
|
||||
Title string
|
||||
Content string
|
||||
ReviewDate *time.Time
|
||||
OwnerID gid.GID
|
||||
CreatedBy gid.GID
|
||||
}
|
||||
|
||||
UpdatePolicyRequest struct {
|
||||
ID gid.GID
|
||||
ExpectedVersion int
|
||||
Name *string
|
||||
Content *string
|
||||
Status *coredata.PolicyStatus
|
||||
ReviewDate *time.Time
|
||||
OwnerID *gid.GID
|
||||
UpdatePolicyVersionRequest struct {
|
||||
ID gid.GID
|
||||
Content string
|
||||
}
|
||||
|
||||
RequestSignatureRequest struct {
|
||||
PolicyVersionID gid.GID
|
||||
RequestedBy gid.GID
|
||||
Signatory gid.GID
|
||||
}
|
||||
)
|
||||
|
||||
@@ -56,38 +56,203 @@ func (s *PolicyService) Get(
|
||||
return policy, nil
|
||||
}
|
||||
|
||||
func (s *PolicyService) PublishVersion(
|
||||
ctx context.Context,
|
||||
policyID gid.GID,
|
||||
publishedBy gid.GID,
|
||||
) (*coredata.Policy, *coredata.PolicyVersion, error) {
|
||||
policy := &coredata.Policy{}
|
||||
policyVersion := &coredata.PolicyVersion{}
|
||||
now := time.Now()
|
||||
|
||||
err := s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(tx pg.Conn) error {
|
||||
if err := policy.LoadByID(ctx, tx, s.svc.scope, policyID); err != nil {
|
||||
return fmt.Errorf("cannot load policy %q: %w", policyID, err)
|
||||
}
|
||||
|
||||
if err := policyVersion.LoadLatestVersion(ctx, tx, s.svc.scope, policyID); err != nil {
|
||||
return fmt.Errorf("cannot load current draft: %w", err)
|
||||
}
|
||||
|
||||
if policyVersion.Status != coredata.PolicyStatusDraft {
|
||||
return fmt.Errorf("cannot publish version")
|
||||
}
|
||||
|
||||
policy.CurrentPublishedVersion = &policyVersion.VersionNumber
|
||||
policy.UpdatedAt = now
|
||||
|
||||
policyVersion.Status = coredata.PolicyStatusPublished
|
||||
policyVersion.PublishedAt = &now
|
||||
policyVersion.PublishedBy = &publishedBy
|
||||
policyVersion.UpdatedAt = now
|
||||
|
||||
if err := policy.Update(ctx, tx, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot update policy: %w", err)
|
||||
}
|
||||
|
||||
if err := policyVersion.Update(ctx, tx, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot update policy version: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return policy, policyVersion, nil
|
||||
}
|
||||
|
||||
func (s *PolicyService) Create(
|
||||
ctx context.Context,
|
||||
req CreatePolicyRequest,
|
||||
) (*coredata.Policy, error) {
|
||||
) (*coredata.Policy, *coredata.PolicyVersion, error) {
|
||||
now := time.Now()
|
||||
policyID, err := gid.NewGID(s.svc.scope.GetTenantID(), coredata.PolicyEntityType)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot create policy global id: %w", err)
|
||||
return nil, nil, fmt.Errorf("cannot create policy global id: %w", err)
|
||||
}
|
||||
|
||||
policyVersionID, err := gid.NewGID(s.svc.scope.GetTenantID(), coredata.PolicyVersionEntityType)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("cannot create policy version global id: %w", err)
|
||||
}
|
||||
|
||||
organization := &coredata.Organization{}
|
||||
policy := &coredata.Policy{
|
||||
ID: policyID,
|
||||
OrganizationID: req.OrganizationID,
|
||||
OwnerID: req.OwnerID,
|
||||
Name: req.Name,
|
||||
Content: req.Content,
|
||||
Status: req.Status,
|
||||
ReviewDate: req.ReviewDate,
|
||||
Title: req.Title,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
policyVersion := &coredata.PolicyVersion{
|
||||
ID: policyVersionID,
|
||||
PolicyID: policyID,
|
||||
VersionNumber: 1,
|
||||
Content: req.Content,
|
||||
CreatedBy: req.CreatedBy,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
err = s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
if err := policy.Insert(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot insert policy: %w", err)
|
||||
}
|
||||
|
||||
if err := policyVersion.Update(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot create policy version: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return policy, policyVersion, nil
|
||||
}
|
||||
|
||||
func (s *PolicyService) UpdateVersion(
|
||||
ctx context.Context,
|
||||
req UpdatePolicyVersionRequest,
|
||||
) (*coredata.PolicyVersion, error) {
|
||||
policyVersion := &coredata.PolicyVersion{}
|
||||
|
||||
err := s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
if err := policyVersion.LoadByID(ctx, conn, s.svc.scope, req.ID); err != nil {
|
||||
return fmt.Errorf("cannot load policy version %q: %w", req.ID, err)
|
||||
}
|
||||
|
||||
if policyVersion.Status != coredata.PolicyStatusDraft {
|
||||
return fmt.Errorf("cannot update published version")
|
||||
}
|
||||
|
||||
policyVersion.Content = req.Content
|
||||
policyVersion.UpdatedAt = time.Now()
|
||||
|
||||
if err := policyVersion.Update(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot update policy version: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return policyVersion, nil
|
||||
}
|
||||
|
||||
func (s *PolicyService) GetVersionSignature(
|
||||
ctx context.Context,
|
||||
signatureID gid.GID,
|
||||
) (*coredata.PolicyVersionSignature, error) {
|
||||
policyVersionSignature := &coredata.PolicyVersionSignature{}
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
return policyVersionSignature.LoadByID(ctx, conn, s.svc.scope, signatureID)
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return policyVersionSignature, nil
|
||||
}
|
||||
|
||||
func (s *PolicyService) RequestSignature(
|
||||
ctx context.Context,
|
||||
req RequestSignatureRequest,
|
||||
) (*coredata.PolicyVersionSignature, error) {
|
||||
policyVersionSignatureID, err := gid.NewGID(s.svc.scope.GetTenantID(), coredata.PolicyVersionSignatureEntityType)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot create policy version signature global id: %w", err)
|
||||
}
|
||||
|
||||
policyVersion, err := s.GetVersion(ctx, req.PolicyVersionID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot get policy version: %w", err)
|
||||
}
|
||||
|
||||
if policyVersion.Status != coredata.PolicyStatusPublished {
|
||||
return nil, fmt.Errorf("cannot request signature for unpublished version")
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
policyVersionSignature := &coredata.PolicyVersionSignature{
|
||||
ID: policyVersionSignatureID,
|
||||
PolicyVersionID: req.PolicyVersionID,
|
||||
State: coredata.PolicyVersionSignatureStateRequested,
|
||||
RequestedBy: req.RequestedBy,
|
||||
RequestedAt: now,
|
||||
SignedBy: req.Signatory,
|
||||
SignedAt: nil,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
err = s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
if err := organization.LoadByID(ctx, conn, s.svc.scope, req.OrganizationID); err != nil {
|
||||
return fmt.Errorf("cannot load organization %q: %w", req.OrganizationID, err)
|
||||
}
|
||||
|
||||
if err := policy.Insert(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot insert policy: %w", err)
|
||||
if err := policyVersionSignature.Insert(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot insert policy version signature: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -98,54 +263,77 @@ func (s *PolicyService) Create(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return policy, nil
|
||||
return policyVersionSignature, nil
|
||||
}
|
||||
|
||||
func (s *PolicyService) Update(
|
||||
func (s *PolicyService) ListSignatures(
|
||||
ctx context.Context,
|
||||
req UpdatePolicyRequest,
|
||||
) (*coredata.Policy, error) {
|
||||
policy := &coredata.Policy{}
|
||||
policyVersionID gid.GID,
|
||||
cursor *page.Cursor[coredata.PolicyVersionSignatureOrderField],
|
||||
) (*page.Page[*coredata.PolicyVersionSignature, coredata.PolicyVersionSignatureOrderField], error) {
|
||||
var policyVersionSignatures coredata.PolicyVersionSignatures
|
||||
|
||||
err := s.svc.pg.WithTx(
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
if err := policy.LoadByID(ctx, conn, s.svc.scope, req.ID); err != nil {
|
||||
return fmt.Errorf("cannot load policy %q: %w", req.ID, err)
|
||||
return policyVersionSignatures.LoadByPolicyVersionID(ctx, conn, s.svc.scope, policyVersionID, cursor)
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return page.NewPage(policyVersionSignatures, cursor), nil
|
||||
}
|
||||
|
||||
func (s *PolicyService) CreateDraft(
|
||||
ctx context.Context,
|
||||
policyID gid.GID,
|
||||
createdBy gid.GID,
|
||||
) (*coredata.PolicyVersion, error) {
|
||||
draftVersionID, err := gid.NewGID(s.svc.scope.GetTenantID(), coredata.PolicyVersionEntityType)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot create policy version global id: %w", err)
|
||||
}
|
||||
|
||||
latestVersion := &coredata.PolicyVersion{}
|
||||
draftVersion := &coredata.PolicyVersion{}
|
||||
now := time.Now()
|
||||
|
||||
err = s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
if err := latestVersion.LoadLatestVersion(ctx, conn, s.svc.scope, policyID); err != nil {
|
||||
return fmt.Errorf("cannot load latest version: %w", err)
|
||||
}
|
||||
|
||||
if req.Name != nil {
|
||||
policy.Name = *req.Name
|
||||
if latestVersion.Status != coredata.PolicyStatusPublished {
|
||||
return fmt.Errorf("cannot create draft from unpublished version")
|
||||
}
|
||||
|
||||
if req.Content != nil {
|
||||
policy.Content = *req.Content
|
||||
}
|
||||
draftVersion.ID = draftVersionID
|
||||
draftVersion.PolicyID = policyID
|
||||
draftVersion.VersionNumber = latestVersion.VersionNumber + 1
|
||||
draftVersion.Content = latestVersion.Content
|
||||
draftVersion.Status = coredata.PolicyStatusDraft
|
||||
draftVersion.CreatedBy = createdBy
|
||||
draftVersion.CreatedAt = now
|
||||
draftVersion.UpdatedAt = now
|
||||
|
||||
if req.Status != nil {
|
||||
policy.Status = *req.Status
|
||||
}
|
||||
|
||||
if req.ReviewDate != nil {
|
||||
policy.ReviewDate = req.ReviewDate
|
||||
}
|
||||
|
||||
if req.OwnerID != nil {
|
||||
policy.OwnerID = *req.OwnerID
|
||||
}
|
||||
|
||||
if err := policy.Update(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot update policy: %w", err)
|
||||
if err := draftVersion.Insert(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot create draft: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return policy, nil
|
||||
return draftVersion, nil
|
||||
}
|
||||
|
||||
func (s *PolicyService) Delete(
|
||||
@@ -162,6 +350,47 @@ func (s *PolicyService) Delete(
|
||||
)
|
||||
}
|
||||
|
||||
func (s *PolicyService) ListVersions(
|
||||
ctx context.Context,
|
||||
policyID gid.GID,
|
||||
cursor *page.Cursor[coredata.PolicyVersionOrderField],
|
||||
) (*page.Page[*coredata.PolicyVersion, coredata.PolicyVersionOrderField], error) {
|
||||
var policyVersions coredata.PolicyVersions
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
return policyVersions.LoadByPolicyID(ctx, conn, s.svc.scope, policyID, cursor)
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return page.NewPage(policyVersions, cursor), nil
|
||||
}
|
||||
|
||||
func (s *PolicyService) GetVersion(
|
||||
ctx context.Context,
|
||||
policyVersionID gid.GID,
|
||||
) (*coredata.PolicyVersion, error) {
|
||||
policyVersion := &coredata.PolicyVersion{}
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
return policyVersion.LoadByID(ctx, conn, s.svc.scope, policyVersionID)
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return policyVersion, nil
|
||||
}
|
||||
|
||||
func (s *PolicyService) ListByOrganizationID(
|
||||
ctx context.Context,
|
||||
organizationID gid.GID,
|
||||
|
||||
@@ -95,8 +95,8 @@ enum PolicyStatus
|
||||
@goModel(model: "github.com/getprobo/probo/pkg/coredata.PolicyStatus") {
|
||||
DRAFT
|
||||
@goEnum(value: "github.com/getprobo/probo/pkg/coredata.PolicyStatusDraft")
|
||||
ACTIVE
|
||||
@goEnum(value: "github.com/getprobo/probo/pkg/coredata.PolicyStatusActive")
|
||||
PUBLISHED
|
||||
@goEnum(value: "github.com/getprobo/probo/pkg/coredata.PolicyStatusPublished")
|
||||
}
|
||||
|
||||
enum EvidenceType
|
||||
@@ -184,7 +184,14 @@ enum TaskOrderField
|
||||
|
||||
enum PolicyOrderField
|
||||
@goModel(model: "github.com/getprobo/probo/pkg/coredata.PolicyOrderField") {
|
||||
NAME
|
||||
TITLE
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.PolicyOrderFieldTitle"
|
||||
)
|
||||
CREATED_AT
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.PolicyOrderFieldCreatedAt"
|
||||
)
|
||||
}
|
||||
|
||||
enum RiskOrderField
|
||||
@@ -260,6 +267,18 @@ enum BusinessImpact
|
||||
@goEnum(value: "github.com/getprobo/probo/pkg/coredata.BusinessImpactCritical")
|
||||
}
|
||||
|
||||
enum PolicyVersionOrderField
|
||||
@goModel(model: "github.com/getprobo/probo/pkg/coredata.PolicyVersionOrderField") {
|
||||
VERSION
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.PolicyVersionOrderFieldVersion"
|
||||
)
|
||||
CREATED_AT
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.PolicyVersionOrderFieldCreatedAt"
|
||||
)
|
||||
}
|
||||
|
||||
# Order Input Types
|
||||
input UserOrder
|
||||
@goModel(
|
||||
@@ -359,6 +378,18 @@ input ConnectorOrder {
|
||||
direction: OrderDirection!
|
||||
}
|
||||
|
||||
input PolicyVersionOrder
|
||||
@goModel(
|
||||
model: "github.com/getprobo/probo/pkg/server/api/console/v1/types.PolicyVersionOrderBy"
|
||||
) {
|
||||
direction: OrderDirection!
|
||||
field: PolicyVersionOrderField!
|
||||
}
|
||||
|
||||
input PolicyVersionFilter {
|
||||
status: PolicyStatus
|
||||
}
|
||||
|
||||
# Core Types
|
||||
type Organization implements Node {
|
||||
id: ID!
|
||||
@@ -631,12 +662,20 @@ type Evidence implements Node {
|
||||
|
||||
type Policy implements Node {
|
||||
id: ID!
|
||||
name: String!
|
||||
status: PolicyStatus!
|
||||
content: String!
|
||||
reviewDate: Datetime
|
||||
title: String!
|
||||
description: String!
|
||||
currentPublishedVersion: Int
|
||||
owner: People! @goField(forceResolver: true)
|
||||
|
||||
versions(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
orderBy: PolicyVersionOrder
|
||||
filter: PolicyVersionFilter
|
||||
): PolicyVersionConnection! @goField(forceResolver: true)
|
||||
|
||||
controls(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
@@ -852,6 +891,16 @@ type VendorRiskAssessmentEdge {
|
||||
node: VendorRiskAssessment!
|
||||
}
|
||||
|
||||
type PolicyVersionConnection {
|
||||
edges: [PolicyVersionEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
type PolicyVersionEdge {
|
||||
cursor: CursorKey!
|
||||
node: PolicyVersion!
|
||||
}
|
||||
|
||||
# Root Types
|
||||
type Query {
|
||||
node(id: ID!): Node!
|
||||
@@ -952,8 +1001,11 @@ type Mutation {
|
||||
|
||||
# Policy mutations
|
||||
createPolicy(input: CreatePolicyInput!): CreatePolicyPayload!
|
||||
updatePolicy(input: UpdatePolicyInput!): UpdatePolicyPayload!
|
||||
deletePolicy(input: DeletePolicyInput!): DeletePolicyPayload!
|
||||
publishPolicyVersion(input: PublishPolicyVersionInput!): PublishPolicyVersionPayload!
|
||||
createDraftPolicyVersion(input: CreateDraftPolicyVersionInput!): CreateDraftPolicyVersionPayload!
|
||||
updatePolicyVersion(input: UpdatePolicyVersionInput!): UpdatePolicyVersionPayload!
|
||||
requestSignature(input: RequestSignatureInput!): RequestSignaturePayload!
|
||||
|
||||
createVendorRiskAssessment(input: CreateVendorRiskAssessmentInput!): CreateVendorRiskAssessmentPayload!
|
||||
}
|
||||
@@ -1224,20 +1276,17 @@ input DeleteVendorComplianceReportInput {
|
||||
|
||||
input CreatePolicyInput {
|
||||
organizationId: ID!
|
||||
name: String!
|
||||
title: String!
|
||||
content: String!
|
||||
status: PolicyStatus!
|
||||
reviewDate: Datetime
|
||||
ownerId: ID!
|
||||
}
|
||||
|
||||
input UpdatePolicyInput {
|
||||
id: ID!
|
||||
name: String
|
||||
title: String
|
||||
content: String
|
||||
status: PolicyStatus
|
||||
reviewDate: Datetime
|
||||
ownerId: ID
|
||||
createdBy: ID
|
||||
}
|
||||
|
||||
input DeletePolicyInput {
|
||||
@@ -1414,6 +1463,7 @@ type DeleteVendorComplianceReportPayload {
|
||||
|
||||
type CreatePolicyPayload {
|
||||
policyEdge: PolicyEdge!
|
||||
policyVersionEdge: PolicyVersionEdge!
|
||||
}
|
||||
|
||||
type UpdatePolicyPayload {
|
||||
@@ -1489,4 +1539,112 @@ input DeleteMesureInput {
|
||||
|
||||
type DeleteMesurePayload {
|
||||
deletedMesureId: ID!
|
||||
}
|
||||
}
|
||||
|
||||
type PolicyVersion implements Node {
|
||||
id: ID!
|
||||
policy: Policy! @goField(forceResolver: true)
|
||||
status: PolicyStatus!
|
||||
version: Int!
|
||||
content: String!
|
||||
changelog: String!
|
||||
|
||||
signatures(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
orderBy: PolicyVersionSignatureOrder
|
||||
): PolicyVersionSignatureConnection! @goField(forceResolver: true)
|
||||
|
||||
publishedBy: People @goField(forceResolver: true)
|
||||
publishedAt: Datetime
|
||||
createdAt: Datetime!
|
||||
updatedAt: Datetime!
|
||||
}
|
||||
|
||||
type PolicyVersionSignatureConnection {
|
||||
edges: [PolicyVersionSignatureEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
type PolicyVersionSignatureEdge {
|
||||
cursor: CursorKey!
|
||||
node: PolicyVersionSignature!
|
||||
}
|
||||
|
||||
input PolicyVersionSignatureOrder {
|
||||
field: PolicyVersionSignatureOrderField!
|
||||
direction: OrderDirection!
|
||||
}
|
||||
|
||||
enum PolicyVersionSignatureState
|
||||
@goModel(model: "github.com/getprobo/probo/pkg/coredata.PolicyVersionSignatureState") {
|
||||
REQUESTED
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.PolicyVersionSignatureStateRequested"
|
||||
)
|
||||
SIGNED
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.PolicyVersionSignatureStateSigned"
|
||||
)
|
||||
}
|
||||
|
||||
enum PolicyVersionSignatureOrderField
|
||||
@goModel(model: "github.com/getprobo/probo/pkg/coredata.PolicyVersionSignatureOrderField") {
|
||||
CREATED_AT
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.PolicyVersionSignatureOrderFieldCreatedAt"
|
||||
)
|
||||
SIGNED_AT
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.PolicyVersionSignatureOrderFieldSignedAt"
|
||||
)
|
||||
}
|
||||
|
||||
type PolicyVersionSignature implements Node {
|
||||
id: ID!
|
||||
policyVersion: PolicyVersion! @goField(forceResolver: true)
|
||||
state: PolicyVersionSignatureState!
|
||||
signedBy: People! @goField(forceResolver: true)
|
||||
signedAt: Datetime
|
||||
requestedAt: Datetime!
|
||||
requestedBy: People! @goField(forceResolver: true)
|
||||
createdAt: Datetime!
|
||||
updatedAt: Datetime!
|
||||
}
|
||||
|
||||
input RequestSignatureInput {
|
||||
policyVersionId: ID!
|
||||
signatoryId: ID!
|
||||
}
|
||||
|
||||
type RequestSignaturePayload {
|
||||
policyVersionSignatureEdge: PolicyVersionSignatureEdge!
|
||||
}
|
||||
|
||||
input PublishPolicyVersionInput {
|
||||
policyId: ID!
|
||||
}
|
||||
|
||||
type PublishPolicyVersionPayload {
|
||||
policyVersion: PolicyVersion!
|
||||
policy: Policy!
|
||||
}
|
||||
|
||||
type CreateDraftPolicyVersionPayload {
|
||||
policyVersionEdge: PolicyVersionEdge!
|
||||
}
|
||||
|
||||
input CreateDraftPolicyVersionInput {
|
||||
policyID: ID!
|
||||
}
|
||||
|
||||
input UpdatePolicyVersionInput {
|
||||
policyVersionId: ID!
|
||||
content: String!
|
||||
}
|
||||
|
||||
type UpdatePolicyVersionPayload {
|
||||
policyVersion: PolicyVersion!
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -44,12 +44,10 @@ func NewPolicyEdge(policy *coredata.Policy, orderBy coredata.PolicyOrderField) *
|
||||
|
||||
func NewPolicy(policy *coredata.Policy) *Policy {
|
||||
return &Policy{
|
||||
ID: policy.ID,
|
||||
Name: policy.Name,
|
||||
Content: policy.Content,
|
||||
CreatedAt: policy.CreatedAt,
|
||||
UpdatedAt: policy.UpdatedAt,
|
||||
Status: policy.Status,
|
||||
ReviewDate: policy.ReviewDate,
|
||||
ID: policy.ID,
|
||||
Title: policy.Title,
|
||||
CurrentPublishedVersion: policy.CurrentPublishedVersion,
|
||||
CreatedAt: policy.CreatedAt,
|
||||
UpdatedAt: policy.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
56
pkg/server/api/console/v1/types/policy_version.go
Normal file
56
pkg/server/api/console/v1/types/policy_version.go
Normal file
@@ -0,0 +1,56 @@
|
||||
// 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 (
|
||||
"github.com/getprobo/probo/pkg/coredata"
|
||||
"github.com/getprobo/probo/pkg/page"
|
||||
)
|
||||
|
||||
type (
|
||||
PolicyVersionOrderBy OrderBy[coredata.PolicyVersionOrderField]
|
||||
)
|
||||
|
||||
func NewPolicyVersionConnection(page *page.Page[*coredata.PolicyVersion, coredata.PolicyVersionOrderField]) *PolicyVersionConnection {
|
||||
edges := make([]*PolicyVersionEdge, len(page.Data))
|
||||
for i, policyVersion := range page.Data {
|
||||
edges[i] = NewPolicyVersionEdge(policyVersion, page.Cursor.OrderBy.Field)
|
||||
}
|
||||
|
||||
return &PolicyVersionConnection{
|
||||
Edges: edges,
|
||||
PageInfo: NewPageInfo(page),
|
||||
}
|
||||
}
|
||||
|
||||
func NewPolicyVersionEdge(policyVersion *coredata.PolicyVersion, orderBy coredata.PolicyVersionOrderField) *PolicyVersionEdge {
|
||||
return &PolicyVersionEdge{
|
||||
Cursor: policyVersion.CursorKey(orderBy),
|
||||
Node: NewPolicyVersion(policyVersion),
|
||||
}
|
||||
}
|
||||
|
||||
func NewPolicyVersion(policyVersion *coredata.PolicyVersion) *PolicyVersion {
|
||||
return &PolicyVersion{
|
||||
ID: policyVersion.ID,
|
||||
Version: policyVersion.VersionNumber,
|
||||
Content: policyVersion.Content,
|
||||
Status: policyVersion.Status,
|
||||
PublishedAt: policyVersion.PublishedAt,
|
||||
Changelog: policyVersion.Changelog,
|
||||
CreatedAt: policyVersion.CreatedAt,
|
||||
UpdatedAt: policyVersion.UpdatedAt,
|
||||
}
|
||||
}
|
||||
54
pkg/server/api/console/v1/types/policy_version_signature.go
Normal file
54
pkg/server/api/console/v1/types/policy_version_signature.go
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/getprobo/probo/pkg/coredata"
|
||||
"github.com/getprobo/probo/pkg/page"
|
||||
)
|
||||
|
||||
type (
|
||||
PolicyVersionSignatureOrderBy OrderBy[coredata.PolicyVersionSignatureOrderField]
|
||||
)
|
||||
|
||||
func NewPolicyVersionSignatureConnection(page *page.Page[*coredata.PolicyVersionSignature, coredata.PolicyVersionSignatureOrderField]) *PolicyVersionSignatureConnection {
|
||||
edges := make([]*PolicyVersionSignatureEdge, len(page.Data))
|
||||
for i, policyVersionSignature := range page.Data {
|
||||
edges[i] = NewPolicyVersionSignatureEdge(policyVersionSignature, page.Cursor.OrderBy.Field)
|
||||
}
|
||||
|
||||
return &PolicyVersionSignatureConnection{
|
||||
Edges: edges,
|
||||
PageInfo: NewPageInfo(page),
|
||||
}
|
||||
}
|
||||
|
||||
func NewPolicyVersionSignatureEdge(policyVersionSignature *coredata.PolicyVersionSignature, orderBy coredata.PolicyVersionSignatureOrderField) *PolicyVersionSignatureEdge {
|
||||
return &PolicyVersionSignatureEdge{
|
||||
Cursor: policyVersionSignature.CursorKey(orderBy),
|
||||
Node: NewPolicyVersionSignature(policyVersionSignature),
|
||||
}
|
||||
}
|
||||
|
||||
func NewPolicyVersionSignature(policyVersionSignature *coredata.PolicyVersionSignature) *PolicyVersionSignature {
|
||||
return &PolicyVersionSignature{
|
||||
ID: policyVersionSignature.ID,
|
||||
State: policyVersionSignature.State,
|
||||
SignedAt: policyVersionSignature.SignedAt,
|
||||
RequestedAt: policyVersionSignature.RequestedAt,
|
||||
CreatedAt: policyVersionSignature.CreatedAt,
|
||||
UpdatedAt: policyVersionSignature.UpdatedAt,
|
||||
}
|
||||
}
|
||||
@@ -104,6 +104,14 @@ type CreateControlPolicyMappingPayload struct {
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
type CreateDraftPolicyVersionInput struct {
|
||||
PolicyID gid.GID `json:"policyID"`
|
||||
}
|
||||
|
||||
type CreateDraftPolicyVersionPayload struct {
|
||||
PolicyVersionEdge *PolicyVersionEdge `json:"policyVersionEdge"`
|
||||
}
|
||||
|
||||
type CreateEvidenceInput struct {
|
||||
TaskID gid.GID `json:"taskId"`
|
||||
Name string `json:"name"`
|
||||
@@ -159,16 +167,15 @@ type CreatePeoplePayload struct {
|
||||
}
|
||||
|
||||
type CreatePolicyInput struct {
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
Name string `json:"name"`
|
||||
Content string `json:"content"`
|
||||
Status coredata.PolicyStatus `json:"status"`
|
||||
ReviewDate *time.Time `json:"reviewDate,omitempty"`
|
||||
OwnerID gid.GID `json:"ownerId"`
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
OwnerID gid.GID `json:"ownerId"`
|
||||
}
|
||||
|
||||
type CreatePolicyPayload struct {
|
||||
PolicyEdge *PolicyEdge `json:"policyEdge"`
|
||||
PolicyEdge *PolicyEdge `json:"policyEdge"`
|
||||
PolicyVersionEdge *PolicyVersionEdge `json:"policyVersionEdge"`
|
||||
}
|
||||
|
||||
type CreateRiskInput struct {
|
||||
@@ -556,15 +563,15 @@ type PeopleEdge struct {
|
||||
}
|
||||
|
||||
type Policy struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Status coredata.PolicyStatus `json:"status"`
|
||||
Content string `json:"content"`
|
||||
ReviewDate *time.Time `json:"reviewDate,omitempty"`
|
||||
Owner *People `json:"owner"`
|
||||
Controls *ControlConnection `json:"controls"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
ID gid.GID `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
CurrentPublishedVersion *int `json:"currentPublishedVersion,omitempty"`
|
||||
Owner *People `json:"owner"`
|
||||
Versions *PolicyVersionConnection `json:"versions"`
|
||||
Controls *ControlConnection `json:"controls"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
func (Policy) IsNode() {}
|
||||
@@ -580,6 +587,76 @@ type PolicyEdge struct {
|
||||
Node *Policy `json:"node"`
|
||||
}
|
||||
|
||||
type PolicyVersion struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Policy *Policy `json:"policy"`
|
||||
Status coredata.PolicyStatus `json:"status"`
|
||||
Version int `json:"version"`
|
||||
Content string `json:"content"`
|
||||
Changelog string `json:"changelog"`
|
||||
Signatures *PolicyVersionSignatureConnection `json:"signatures"`
|
||||
PublishedBy *People `json:"publishedBy,omitempty"`
|
||||
PublishedAt *time.Time `json:"publishedAt,omitempty"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
func (PolicyVersion) IsNode() {}
|
||||
func (this PolicyVersion) GetID() gid.GID { return this.ID }
|
||||
|
||||
type PolicyVersionConnection struct {
|
||||
Edges []*PolicyVersionEdge `json:"edges"`
|
||||
PageInfo *PageInfo `json:"pageInfo"`
|
||||
}
|
||||
|
||||
type PolicyVersionEdge struct {
|
||||
Cursor page.CursorKey `json:"cursor"`
|
||||
Node *PolicyVersion `json:"node"`
|
||||
}
|
||||
|
||||
type PolicyVersionFilter struct {
|
||||
Status *coredata.PolicyStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
type PolicyVersionSignature struct {
|
||||
ID gid.GID `json:"id"`
|
||||
PolicyVersion *PolicyVersion `json:"policyVersion"`
|
||||
State coredata.PolicyVersionSignatureState `json:"state"`
|
||||
SignedBy *People `json:"signedBy"`
|
||||
SignedAt *time.Time `json:"signedAt,omitempty"`
|
||||
RequestedAt time.Time `json:"requestedAt"`
|
||||
RequestedBy *People `json:"requestedBy"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
func (PolicyVersionSignature) IsNode() {}
|
||||
func (this PolicyVersionSignature) GetID() gid.GID { return this.ID }
|
||||
|
||||
type PolicyVersionSignatureConnection struct {
|
||||
Edges []*PolicyVersionSignatureEdge `json:"edges"`
|
||||
PageInfo *PageInfo `json:"pageInfo"`
|
||||
}
|
||||
|
||||
type PolicyVersionSignatureEdge struct {
|
||||
Cursor page.CursorKey `json:"cursor"`
|
||||
Node *PolicyVersionSignature `json:"node"`
|
||||
}
|
||||
|
||||
type PolicyVersionSignatureOrder struct {
|
||||
Field coredata.PolicyVersionSignatureOrderField `json:"field"`
|
||||
Direction page.OrderDirection `json:"direction"`
|
||||
}
|
||||
|
||||
type PublishPolicyVersionInput struct {
|
||||
PolicyID gid.GID `json:"policyId"`
|
||||
}
|
||||
|
||||
type PublishPolicyVersionPayload struct {
|
||||
PolicyVersion *PolicyVersion `json:"policyVersion"`
|
||||
Policy *Policy `json:"policy"`
|
||||
}
|
||||
|
||||
type Query struct {
|
||||
}
|
||||
|
||||
@@ -603,6 +680,15 @@ type RequestEvidencePayload struct {
|
||||
EvidenceEdge *EvidenceEdge `json:"evidenceEdge"`
|
||||
}
|
||||
|
||||
type RequestSignatureInput struct {
|
||||
PolicyVersionID gid.GID `json:"policyVersionId"`
|
||||
SignatoryID gid.GID `json:"signatoryId"`
|
||||
}
|
||||
|
||||
type RequestSignaturePayload struct {
|
||||
PolicyVersionSignatureEdge *PolicyVersionSignatureEdge `json:"policyVersionSignatureEdge"`
|
||||
}
|
||||
|
||||
type Risk struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
@@ -720,18 +806,26 @@ type UpdatePeoplePayload struct {
|
||||
}
|
||||
|
||||
type UpdatePolicyInput struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
Content *string `json:"content,omitempty"`
|
||||
Status *coredata.PolicyStatus `json:"status,omitempty"`
|
||||
ReviewDate *time.Time `json:"reviewDate,omitempty"`
|
||||
OwnerID *gid.GID `json:"ownerId,omitempty"`
|
||||
ID gid.GID `json:"id"`
|
||||
Title *string `json:"title,omitempty"`
|
||||
Content *string `json:"content,omitempty"`
|
||||
OwnerID *gid.GID `json:"ownerId,omitempty"`
|
||||
CreatedBy *gid.GID `json:"createdBy,omitempty"`
|
||||
}
|
||||
|
||||
type UpdatePolicyPayload struct {
|
||||
Policy *Policy `json:"policy"`
|
||||
}
|
||||
|
||||
type UpdatePolicyVersionInput struct {
|
||||
PolicyVersionID gid.GID `json:"policyVersionId"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type UpdatePolicyVersionPayload struct {
|
||||
PolicyVersion *PolicyVersion `json:"policyVersion"`
|
||||
}
|
||||
|
||||
type UpdateRiskInput struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
|
||||
@@ -1008,41 +1008,29 @@ func (r *mutationResolver) DeleteVendorComplianceReport(ctx context.Context, inp
|
||||
func (r *mutationResolver) CreatePolicy(ctx context.Context, input types.CreatePolicyInput) (*types.CreatePolicyPayload, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, input.OrganizationID.TenantID())
|
||||
|
||||
policy, err := svc.Policies.Create(ctx, probo.CreatePolicyRequest{
|
||||
OrganizationID: input.OrganizationID,
|
||||
Name: input.Name,
|
||||
Content: input.Content,
|
||||
Status: input.Status,
|
||||
ReviewDate: input.ReviewDate,
|
||||
OwnerID: input.OwnerID,
|
||||
})
|
||||
user := UserFromContext(ctx)
|
||||
people, err := svc.Peoples.GetByUserID(ctx, user.ID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get people: %w", err))
|
||||
}
|
||||
|
||||
policy, policyVersion, err := svc.Policies.Create(
|
||||
ctx,
|
||||
probo.CreatePolicyRequest{
|
||||
OrganizationID: input.OrganizationID,
|
||||
Title: input.Title,
|
||||
OwnerID: input.OwnerID,
|
||||
Content: input.Content,
|
||||
CreatedBy: people.ID,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot create policy: %w", err))
|
||||
}
|
||||
|
||||
return &types.CreatePolicyPayload{
|
||||
PolicyEdge: types.NewPolicyEdge(policy, coredata.PolicyOrderFieldCreatedAt),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdatePolicy is the resolver for the updatePolicy field.
|
||||
func (r *mutationResolver) UpdatePolicy(ctx context.Context, input types.UpdatePolicyInput) (*types.UpdatePolicyPayload, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, input.ID.TenantID())
|
||||
|
||||
policy, err := svc.Policies.Update(ctx, probo.UpdatePolicyRequest{
|
||||
ID: input.ID,
|
||||
Name: input.Name,
|
||||
Content: input.Content,
|
||||
Status: input.Status,
|
||||
ReviewDate: input.ReviewDate,
|
||||
OwnerID: input.OwnerID,
|
||||
})
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot update policy: %w", err))
|
||||
}
|
||||
|
||||
return &types.UpdatePolicyPayload{
|
||||
Policy: types.NewPolicy(policy),
|
||||
PolicyEdge: types.NewPolicyEdge(policy, coredata.PolicyOrderFieldTitle),
|
||||
PolicyVersionEdge: types.NewPolicyVersionEdge(policyVersion, coredata.PolicyVersionOrderFieldCreatedAt),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1060,16 +1048,96 @@ func (r *mutationResolver) DeletePolicy(ctx context.Context, input types.DeleteP
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PublishPolicyVersion is the resolver for the publishPolicyVersion field.
|
||||
func (r *mutationResolver) PublishPolicyVersion(ctx context.Context, input types.PublishPolicyVersionInput) (*types.PublishPolicyVersionPayload, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, input.PolicyID.TenantID())
|
||||
user := UserFromContext(ctx)
|
||||
|
||||
people, err := svc.Peoples.GetByUserID(ctx, user.ID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get people: %w", err))
|
||||
}
|
||||
|
||||
policy, policyVersion, err := svc.Policies.PublishVersion(ctx, input.PolicyID, people.ID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot publish policy version: %w", err))
|
||||
}
|
||||
|
||||
return &types.PublishPolicyVersionPayload{
|
||||
PolicyVersion: types.NewPolicyVersion(policyVersion),
|
||||
Policy: types.NewPolicy(policy),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateDraftPolicyVersion is the resolver for the createDraftPolicyVersion field.
|
||||
func (r *mutationResolver) CreateDraftPolicyVersion(ctx context.Context, input types.CreateDraftPolicyVersionInput) (*types.CreateDraftPolicyVersionPayload, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, input.PolicyID.TenantID())
|
||||
|
||||
user := UserFromContext(ctx)
|
||||
people, err := svc.Peoples.GetByUserID(ctx, user.ID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get people: %w", err))
|
||||
}
|
||||
|
||||
policyVersion, err := svc.Policies.CreateDraft(ctx, input.PolicyID, people.ID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot create draft policy version: %w", err))
|
||||
}
|
||||
|
||||
return &types.CreateDraftPolicyVersionPayload{
|
||||
PolicyVersionEdge: types.NewPolicyVersionEdge(policyVersion, coredata.PolicyVersionOrderFieldCreatedAt),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdatePolicyVersion is the resolver for the updatePolicyVersion field.
|
||||
func (r *mutationResolver) UpdatePolicyVersion(ctx context.Context, input types.UpdatePolicyVersionInput) (*types.UpdatePolicyVersionPayload, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, input.PolicyVersionID.TenantID())
|
||||
|
||||
policyVersion, err := svc.Policies.UpdateVersion(ctx, probo.UpdatePolicyVersionRequest{
|
||||
ID: input.PolicyVersionID,
|
||||
Content: input.Content,
|
||||
})
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot update policy version: %w", err))
|
||||
}
|
||||
|
||||
return &types.UpdatePolicyVersionPayload{
|
||||
PolicyVersion: types.NewPolicyVersion(policyVersion),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RequestSignature is the resolver for the requestSignature field.
|
||||
func (r *mutationResolver) RequestSignature(ctx context.Context, input types.RequestSignatureInput) (*types.RequestSignaturePayload, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, input.PolicyVersionID.TenantID())
|
||||
|
||||
user := UserFromContext(ctx)
|
||||
|
||||
people, err := svc.Peoples.GetByUserID(ctx, user.ID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get people: %w", err))
|
||||
}
|
||||
|
||||
policyVersionSignature, err := svc.Policies.RequestSignature(
|
||||
ctx,
|
||||
probo.RequestSignatureRequest{
|
||||
PolicyVersionID: input.PolicyVersionID,
|
||||
RequestedBy: people.ID,
|
||||
Signatory: input.SignatoryID,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot request signature: %w", err))
|
||||
}
|
||||
|
||||
return &types.RequestSignaturePayload{
|
||||
PolicyVersionSignatureEdge: types.NewPolicyVersionSignatureEdge(policyVersionSignature, coredata.PolicyVersionSignatureOrderFieldCreatedAt),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateVendorRiskAssessment is the resolver for the createVendorRiskAssessment field.
|
||||
func (r *mutationResolver) CreateVendorRiskAssessment(ctx context.Context, input types.CreateVendorRiskAssessmentInput) (*types.CreateVendorRiskAssessmentPayload, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, input.VendorID.TenantID())
|
||||
|
||||
fmt.Println("input.AssessedBy", input.AssessedBy)
|
||||
fmt.Println("input.ExpiresAt", input.ExpiresAt)
|
||||
fmt.Println("input.DataSensitivity", input.DataSensitivity)
|
||||
fmt.Println("input.BusinessImpact", input.BusinessImpact)
|
||||
fmt.Println("input.Notes", input.Notes)
|
||||
|
||||
vendorRiskAssessment, err := svc.Vendors.CreateRiskAssessment(
|
||||
ctx,
|
||||
probo.CreateVendorRiskAssessmentRequest{
|
||||
@@ -1225,7 +1293,7 @@ func (r *organizationResolver) Policies(ctx context.Context, obj *types.Organiza
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
|
||||
pageOrderBy := page.OrderBy[coredata.PolicyOrderField]{
|
||||
Field: coredata.PolicyOrderFieldName,
|
||||
Field: coredata.PolicyOrderFieldTitle,
|
||||
Direction: page.OrderDirectionDesc,
|
||||
}
|
||||
if orderBy != nil {
|
||||
@@ -1313,6 +1381,31 @@ func (r *policyResolver) Owner(ctx context.Context, obj *types.Policy) (*types.P
|
||||
return types.NewPeople(owner), nil
|
||||
}
|
||||
|
||||
// Versions is the resolver for the versions field.
|
||||
func (r *policyResolver) Versions(ctx context.Context, obj *types.Policy, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.PolicyVersionOrderBy, filter *types.PolicyVersionFilter) (*types.PolicyVersionConnection, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
|
||||
pageOrderBy := page.OrderBy[coredata.PolicyVersionOrderField]{
|
||||
Field: coredata.PolicyVersionOrderFieldCreatedAt,
|
||||
Direction: page.OrderDirectionDesc,
|
||||
}
|
||||
if orderBy != nil {
|
||||
pageOrderBy = page.OrderBy[coredata.PolicyVersionOrderField]{
|
||||
Field: orderBy.Field,
|
||||
Direction: orderBy.Direction,
|
||||
}
|
||||
}
|
||||
|
||||
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||
|
||||
page, err := svc.Policies.ListVersions(ctx, obj.ID, cursor)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot list policy versions: %w", err))
|
||||
}
|
||||
|
||||
return types.NewPolicyVersionConnection(page), nil
|
||||
}
|
||||
|
||||
// Controls is the resolver for the controls field.
|
||||
func (r *policyResolver) Controls(ctx context.Context, obj *types.Policy, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy) (*types.ControlConnection, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
@@ -1338,6 +1431,120 @@ func (r *policyResolver) Controls(ctx context.Context, obj *types.Policy, first
|
||||
return types.NewControlConnection(page), nil
|
||||
}
|
||||
|
||||
// Policy is the resolver for the policy field.
|
||||
func (r *policyVersionResolver) Policy(ctx context.Context, obj *types.PolicyVersion) (*types.Policy, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
|
||||
policyVersion, err := svc.Policies.GetVersion(ctx, obj.ID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get policy version: %w", err))
|
||||
}
|
||||
|
||||
policy, err := svc.Policies.Get(ctx, policyVersion.PolicyID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get policy: %w", err))
|
||||
}
|
||||
|
||||
return types.NewPolicy(policy), nil
|
||||
}
|
||||
|
||||
// Signatures is the resolver for the signatures field.
|
||||
func (r *policyVersionResolver) Signatures(ctx context.Context, obj *types.PolicyVersion, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.PolicyVersionSignatureOrder) (*types.PolicyVersionSignatureConnection, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
|
||||
pageOrderBy := page.OrderBy[coredata.PolicyVersionSignatureOrderField]{
|
||||
Field: coredata.PolicyVersionSignatureOrderFieldCreatedAt,
|
||||
Direction: page.OrderDirectionDesc,
|
||||
}
|
||||
if orderBy != nil {
|
||||
pageOrderBy = page.OrderBy[coredata.PolicyVersionSignatureOrderField]{
|
||||
Field: orderBy.Field,
|
||||
Direction: orderBy.Direction,
|
||||
}
|
||||
}
|
||||
|
||||
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||
|
||||
page, err := svc.Policies.ListSignatures(ctx, obj.ID, cursor)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot list policy version signatures: %w", err))
|
||||
}
|
||||
|
||||
return types.NewPolicyVersionSignatureConnection(page), nil
|
||||
}
|
||||
|
||||
// PublishedBy is the resolver for the publishedBy field.
|
||||
func (r *policyVersionResolver) PublishedBy(ctx context.Context, obj *types.PolicyVersion) (*types.People, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
|
||||
policyVersion, err := svc.Policies.GetVersion(ctx, obj.ID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get policy version: %w", err))
|
||||
}
|
||||
|
||||
if policyVersion.PublishedBy == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
people, err := svc.Peoples.Get(ctx, *policyVersion.PublishedBy)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get people: %w", err))
|
||||
}
|
||||
|
||||
return types.NewPeople(people), nil
|
||||
}
|
||||
|
||||
// PolicyVersion is the resolver for the policyVersion field.
|
||||
func (r *policyVersionSignatureResolver) PolicyVersion(ctx context.Context, obj *types.PolicyVersionSignature) (*types.PolicyVersion, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
|
||||
policyVersionSignature, err := svc.Policies.GetVersionSignature(ctx, obj.ID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get policy version signature: %w", err))
|
||||
}
|
||||
|
||||
policyVersion, err := svc.Policies.GetVersion(ctx, policyVersionSignature.PolicyVersionID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get policy version: %w", err))
|
||||
}
|
||||
|
||||
return types.NewPolicyVersion(policyVersion), nil
|
||||
}
|
||||
|
||||
// SignedBy is the resolver for the signedBy field.
|
||||
func (r *policyVersionSignatureResolver) SignedBy(ctx context.Context, obj *types.PolicyVersionSignature) (*types.People, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
|
||||
policyVersionSignature, err := svc.Policies.GetVersionSignature(ctx, obj.ID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get policy version signature: %w", err))
|
||||
}
|
||||
|
||||
people, err := svc.Peoples.Get(ctx, policyVersionSignature.SignedBy)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get people: %w", err))
|
||||
}
|
||||
|
||||
return types.NewPeople(people), nil
|
||||
}
|
||||
|
||||
// RequestedBy is the resolver for the requestedBy field.
|
||||
func (r *policyVersionSignatureResolver) RequestedBy(ctx context.Context, obj *types.PolicyVersionSignature) (*types.People, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
|
||||
policyVersionSignature, err := svc.Policies.GetVersionSignature(ctx, obj.ID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get policy version signature: %w", err))
|
||||
}
|
||||
|
||||
people, err := svc.Peoples.Get(ctx, policyVersionSignature.RequestedBy)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get people: %w", err))
|
||||
}
|
||||
|
||||
return types.NewPeople(people), nil
|
||||
}
|
||||
|
||||
// Node is the resolver for the node field.
|
||||
func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, id.TenantID())
|
||||
@@ -1417,6 +1624,18 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error
|
||||
panic(fmt.Errorf("cannot get vendor compliance report: %w", err))
|
||||
}
|
||||
return types.NewVendorComplianceReport(vendorComplianceReport), nil
|
||||
case coredata.PolicyVersionEntityType:
|
||||
policyVersion, err := svc.Policies.GetVersion(ctx, id)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get policy version: %w", err))
|
||||
}
|
||||
return types.NewPolicyVersion(policyVersion), nil
|
||||
case coredata.PolicyVersionSignatureEntityType:
|
||||
policyVersionSignature, err := svc.Policies.GetVersionSignature(ctx, id)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get policy version signature: %w", err))
|
||||
}
|
||||
return types.NewPolicyVersionSignature(policyVersionSignature), nil
|
||||
default:
|
||||
}
|
||||
|
||||
@@ -1579,7 +1798,7 @@ func (r *taskResolver) Evidences(ctx context.Context, obj *types.Task, first *in
|
||||
func (r *userResolver) People(ctx context.Context, obj *types.User, organizationID gid.GID) (*types.People, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, organizationID.TenantID())
|
||||
|
||||
people, err := svc.Peoples.GetByUserID(ctx, organizationID, obj.ID)
|
||||
people, err := svc.Peoples.GetByUserID(ctx, obj.ID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to get people: %w", err))
|
||||
}
|
||||
@@ -1778,6 +1997,14 @@ func (r *Resolver) Organization() schema.OrganizationResolver { return &organiza
|
||||
// Policy returns schema.PolicyResolver implementation.
|
||||
func (r *Resolver) Policy() schema.PolicyResolver { return &policyResolver{r} }
|
||||
|
||||
// PolicyVersion returns schema.PolicyVersionResolver implementation.
|
||||
func (r *Resolver) PolicyVersion() schema.PolicyVersionResolver { return &policyVersionResolver{r} }
|
||||
|
||||
// PolicyVersionSignature returns schema.PolicyVersionSignatureResolver implementation.
|
||||
func (r *Resolver) PolicyVersionSignature() schema.PolicyVersionSignatureResolver {
|
||||
return &policyVersionSignatureResolver{r}
|
||||
}
|
||||
|
||||
// Query returns schema.QueryResolver implementation.
|
||||
func (r *Resolver) Query() schema.QueryResolver { return &queryResolver{r} }
|
||||
|
||||
@@ -1813,6 +2040,8 @@ type mesureResolver struct{ *Resolver }
|
||||
type mutationResolver struct{ *Resolver }
|
||||
type organizationResolver struct{ *Resolver }
|
||||
type policyResolver struct{ *Resolver }
|
||||
type policyVersionResolver struct{ *Resolver }
|
||||
type policyVersionSignatureResolver struct{ *Resolver }
|
||||
type queryResolver struct{ *Resolver }
|
||||
type riskResolver struct{ *Resolver }
|
||||
type taskResolver struct{ *Resolver }
|
||||
|
||||
Reference in New Issue
Block a user