Add policy owner

Signed-off-by: gearnode <bryan@frimin.fr>
This commit is contained in:
gearnode
2025-03-05 11:25:08 +01:00
parent de4e91cab2
commit b214b0930c
18 changed files with 1232 additions and 317 deletions

View File

@@ -0,0 +1,73 @@
import { useState, useEffect } from "react";
import { graphql, useFragment } from "react-relay";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { PeopleSelector_organization$key } from "./__generated__/PeopleSelector_organization.graphql";
const peopleSelectorFragment = graphql`
fragment PeopleSelector_organization on Organization {
id
peoples(first: 100) {
edges {
node {
id
fullName
primaryEmailAddress
}
}
}
}
`;
interface PeopleSelectorProps {
organizationRef: PeopleSelector_organization$key;
selectedPersonId: string | null;
onSelect: (personId: string) => void;
placeholder?: string;
required?: boolean;
}
export default function PeopleSelector({
organizationRef,
selectedPersonId,
onSelect,
placeholder = "Select a person",
required = false,
}: PeopleSelectorProps) {
const organization = useFragment(peopleSelectorFragment, organizationRef);
const [value, setValue] = useState<string>(selectedPersonId || "");
useEffect(() => {
if (selectedPersonId) {
setValue(selectedPersonId);
}
}, [selectedPersonId]);
const handleValueChange = (newValue: string) => {
setValue(newValue);
onSelect(newValue);
};
return (
<Select value={value} onValueChange={handleValueChange} required={required}>
<SelectTrigger>
<SelectValue placeholder={placeholder} />
</SelectTrigger>
<SelectContent>
{organization.peoples?.edges?.map(
(edge) =>
edge?.node && (
<SelectItem key={edge.node.id} value={edge.node.id}>
{edge.node.fullName} ({edge.node.primaryEmailAddress})
</SelectItem>
)
)}
</SelectContent>
</Select>
);
}

View File

@@ -0,0 +1,108 @@
/**
* @generated SignedSource<<40624c6362024ecac90d44c5dd174a1c>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ReaderFragment } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type PeopleSelector_organization$data = {
readonly id: string;
readonly peoples: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly fullName: string;
readonly id: string;
readonly primaryEmailAddress: string;
};
}>;
};
readonly " $fragmentType": "PeopleSelector_organization";
};
export type PeopleSelector_organization$key = {
readonly " $data"?: PeopleSelector_organization$data;
readonly " $fragmentSpreads": FragmentRefs<"PeopleSelector_organization">;
};
const node: ReaderFragment = (function(){
var v0 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
};
return {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "PeopleSelector_organization",
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": [
{
"kind": "Literal",
"name": "first",
"value": 100
}
],
"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": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "primaryEmailAddress",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "peoples(first:100)"
}
],
"type": "Organization",
"abstractKey": null
};
})();
(node as any).hash = "33e28b2889fa1180f6bf491e428fbafd";
export default node;

View File

@@ -1,16 +1,34 @@
import { useState } from "react"; import { useState, useEffect } from "react";
import { useNavigate, useParams } from "react-router"; import { useNavigate, useParams } from "react-router";
import { ConnectionHandler, graphql, useMutation } from "react-relay"; import {
ConnectionHandler,
graphql,
useMutation,
useQueryLoader,
usePreloadedQuery,
PreloadedQuery,
} from "react-relay";
import { Helmet } from "react-helmet-async"; import { Helmet } from "react-helmet-async";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { useToast } from "@/hooks/use-toast"; import { useToast } from "@/hooks/use-toast";
import { FileText, Calendar } from "lucide-react"; import { FileText, Calendar, User } from "lucide-react";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import PolicyEditor from "@/components/PolicyEditor"; import PolicyEditor from "@/components/PolicyEditor";
import PeopleSelector from "@/components/PeopleSelector";
import { Suspense } from "react";
import type { CreatePolicyPageMutation } from "./__generated__/CreatePolicyPageMutation.graphql"; import type { CreatePolicyPageMutation } from "./__generated__/CreatePolicyPageMutation.graphql";
import type { CreatePolicyPageQuery as CreatePolicyPageQueryType } from "./__generated__/CreatePolicyPageQuery.graphql";
const CreatePolicyQuery = graphql`
query CreatePolicyPageQuery($organizationId: ID!) {
organization: node(id: $organizationId) {
...PeopleSelector_organization
}
}
`;
const CreatePolicyMutation = graphql` const CreatePolicyMutation = graphql`
mutation CreatePolicyPageMutation( mutation CreatePolicyPageMutation(
@@ -25,19 +43,32 @@ const CreatePolicyMutation = graphql`
content content
status status
reviewDate reviewDate
owner {
id
fullName
}
} }
} }
} }
} }
`; `;
export default function CreatePolicyPage() { function CreatePolicyForm({
queryRef,
}: {
queryRef: PreloadedQuery<CreatePolicyPageQueryType>;
}) {
const navigate = useNavigate(); const navigate = useNavigate();
const { organizationId } = useParams(); const { organizationId } = useParams();
const data = usePreloadedQuery<CreatePolicyPageQueryType>(
CreatePolicyQuery,
queryRef
);
const [name, setName] = useState(""); const [name, setName] = useState("");
const [content, setContent] = useState(""); const [content, setContent] = useState("");
const [status, setStatus] = useState<"DRAFT" | "ACTIVE">("DRAFT"); const [status, setStatus] = useState<"DRAFT" | "ACTIVE">("DRAFT");
const [reviewDate, setReviewDate] = useState(""); const [reviewDate, setReviewDate] = useState("");
const [ownerId, setOwnerId] = useState<string | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const { toast } = useToast(); const { toast } = useToast();
@@ -53,6 +84,16 @@ export default function CreatePolicyPage() {
const handleSubmit = (e: React.FormEvent) => { const handleSubmit = (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
if (!ownerId) {
toast({
title: "Error",
description: "Please select an owner for the policy.",
variant: "destructive",
});
return;
}
setIsSubmitting(true); setIsSubmitting(true);
// Convert reviewDate string to ISO format for the API // Convert reviewDate string to ISO format for the API
@@ -67,6 +108,7 @@ export default function CreatePolicyPage() {
content, content,
status, status,
reviewDate: reviewDateValue, reviewDate: reviewDateValue,
ownerId,
}; };
commitMutation({ commitMutation({
@@ -184,6 +226,20 @@ export default function CreatePolicyPage() {
</RadioGroup> </RadioGroup>
</div> </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"> <div className="space-y-2">
<Label <Label
htmlFor="reviewDate" htmlFor="reviewDate"
@@ -222,3 +278,38 @@ export default function CreatePolicyPage() {
</> </>
); );
} }
function CreatePolicyPageFallback() {
return (
<div className="container mx-auto py-6">
<div className="flex items-center mb-6">
<div className="mr-4">
<div className="h-12 w-12 bg-muted animate-pulse rounded-lg" />
</div>
<div>
<div className="h-8 w-48 bg-muted animate-pulse rounded mb-2" />
<div className="h-4 w-64 bg-muted animate-pulse rounded" />
</div>
</div>
<div className="bg-muted animate-pulse rounded-lg h-[600px]" />
</div>
);
}
export default function CreatePolicyPage() {
const { organizationId } = useParams();
const [queryRef, loadQuery] =
useQueryLoader<CreatePolicyPageQueryType>(CreatePolicyQuery);
useEffect(() => {
if (organizationId) {
loadQuery({ organizationId });
}
}, [organizationId, loadQuery]);
return (
<Suspense fallback={<CreatePolicyPageFallback />}>
{queryRef && <CreatePolicyForm queryRef={queryRef} />}
</Suspense>
);
}

View File

@@ -6,7 +6,7 @@ import {
usePreloadedQuery, usePreloadedQuery,
useQueryLoader, useQueryLoader,
} from "react-relay"; } from "react-relay";
import { Edit, Download, Shield, User, FileText } from "lucide-react"; import { Edit, Download, Shield, User, FileText, Calendar } from "lucide-react";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
@@ -26,6 +26,11 @@ const PolicyOverviewPageQuery = graphql`
updatedAt updatedAt
reviewDate reviewDate
status status
owner {
id
fullName
primaryEmailAddress
}
} }
} }
} }
@@ -70,7 +75,72 @@ function PolicyOverviewPageContent({
}; };
return ( return (
<>
<Helmet>
<title>{policy.name} - Probo Console</title>
</Helmet>
<div className="container mx-auto py-6"> <div className="container mx-auto py-6">
<div className="flex justify-between items-start mb-6">
<div className="flex items-center">
<div className="mr-4">
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10">
<FileText className="h-6 w-6 text-primary" />
</div>
</div>
<div>
<h1 className="text-2xl font-bold">{policy.name}</h1>
<div className="flex items-center gap-2 text-muted-foreground">
<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" />
<span>{policy.owner.fullName}</span>
</div>
)}
{policy.reviewDate && (
<div className="flex items-center gap-1">
<Calendar className="h-3 w-3" />
<span>Review: {formatDate(policy.reviewDate)}</span>
</div>
)}
</div>
</div>
</div>
<div className="flex gap-2">
<Button variant="outline" size="sm" asChild>
<Link
to={`/organizations/${organizationId}/policies/${policy.id}/edit`}
>
<Edit className="h-4 w-4 mr-2" />
Edit
</Link>
</Button>
<Button
variant="outline"
size="sm"
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>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="lg:col-span-2"> <div className="lg:col-span-2">
<Card className="border shadow-sm"> <Card className="border shadow-sm">
@@ -107,15 +177,6 @@ function PolicyOverviewPageContent({
</Badge> </Badge>
)} )}
</div> </div>
<div className="ml-auto">
<Button
variant="outline"
size="icon"
className="rounded-full"
>
<Download className="h-5 w-5" />
</Button>
</div>
</div> </div>
<h1 className="text-3xl font-bold mb-3">{policy.name}</h1> <h1 className="text-3xl font-bold mb-3">{policy.name}</h1>
@@ -167,7 +228,9 @@ function PolicyOverviewPageContent({
<div className="prose prose-sm md:prose-base lg:prose-lg max-w-none"> <div className="prose prose-sm md:prose-base lg:prose-lg max-w-none">
<div <div
className="policy-content" className="policy-content"
dangerouslySetInnerHTML={{ __html: policy.content || "" }} dangerouslySetInnerHTML={{
__html: policy.content || "",
}}
/> />
</div> </div>
</TabsContent> </TabsContent>
@@ -203,7 +266,9 @@ function PolicyOverviewPageContent({
<FileText className="h-4 w-4" /> <FileText className="h-4 w-4" />
<span className="text-sm">Last Updated</span> <span className="text-sm">Last Updated</span>
</div> </div>
<p className="font-medium">{formatDate(policy.updatedAt)}</p> <p className="font-medium">
{formatDate(policy.updatedAt)}
</p>
</div> </div>
<div> <div>
@@ -223,7 +288,9 @@ function PolicyOverviewPageContent({
<User className="h-4 w-4" /> <User className="h-4 w-4" />
<span className="text-sm">Owner</span> <span className="text-sm">Owner</span>
</div> </div>
<p className="font-medium">Jane Smith, CISO</p> <p className="font-medium">
{policy.owner ? policy.owner.fullName : "Not assigned"}
</p>
</div> </div>
</div> </div>
</CardContent> </CardContent>
@@ -242,8 +309,8 @@ function PolicyOverviewPageContent({
<CardContent className="p-6"> <CardContent className="p-6">
<h3 className="text-red-500 font-semibold mb-3">Danger Zone</h3> <h3 className="text-red-500 font-semibold mb-3">Danger Zone</h3>
<p className="text-sm text-muted-foreground mb-4"> <p className="text-sm text-muted-foreground mb-4">
Permanently delete this policy and all of its data. This action Permanently delete this policy and all of its data. This
cannot be undone. action cannot be undone.
</p> </p>
<Button variant="destructive" className="w-full"> <Button variant="destructive" className="w-full">
<svg <svg
@@ -269,6 +336,7 @@ function PolicyOverviewPageContent({
</div> </div>
</div> </div>
</div> </div>
</>
); );
} }
@@ -377,13 +445,8 @@ export default function PolicyOverviewPage() {
}, [loadQuery, policyId]); }, [loadQuery, policyId]);
return ( return (
<>
<Helmet>
<title>Policy Details - Probo Console</title>
</Helmet>
<Suspense fallback={<PolicyOverviewPageFallback />}> <Suspense fallback={<PolicyOverviewPageFallback />}>
{queryRef && <PolicyOverviewPageContent queryRef={queryRef} />} {queryRef && <PolicyOverviewPageContent queryRef={queryRef} />}
</Suspense> </Suspense>
</>
); );
} }

View File

@@ -13,16 +13,17 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { toast } from "@/hooks/use-toast"; import { toast } from "@/hooks/use-toast";
import { FileText, Calendar } from "lucide-react"; import { FileText, Calendar, User } from "lucide-react";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Suspense } from "react"; import { Suspense } from "react";
import PolicyEditor from "@/components/PolicyEditor"; import PolicyEditor from "@/components/PolicyEditor";
import PeopleSelector from "@/components/PeopleSelector";
import type { UpdatePolicyPageQuery as UpdatePolicyPageQueryType } from "./__generated__/UpdatePolicyPageQuery.graphql"; import type { UpdatePolicyPageQuery as UpdatePolicyPageQueryType } from "./__generated__/UpdatePolicyPageQuery.graphql";
import type { UpdatePolicyPageMutation as UpdatePolicyPageMutationType } from "./__generated__/UpdatePolicyPageMutation.graphql"; import type { UpdatePolicyPageMutation as UpdatePolicyPageMutationType } from "./__generated__/UpdatePolicyPageMutation.graphql";
const UpdatePolicyPageQuery = graphql` const UpdatePolicyPageQuery = graphql`
query UpdatePolicyPageQuery($policyId: ID!) { query UpdatePolicyPageQuery($policyId: ID!, $organizationId: ID!) {
node(id: $policyId) { policy: node(id: $policyId) {
id id
... on Policy { ... on Policy {
name name
@@ -30,9 +31,16 @@ const UpdatePolicyPageQuery = graphql`
status status
version version
reviewDate reviewDate
owner {
id
fullName
} }
} }
} }
organization: node(id: $organizationId) {
...PeopleSelector_organization
}
}
`; `;
const UpdatePolicyMutation = graphql` const UpdatePolicyMutation = graphql`
@@ -45,6 +53,10 @@ const UpdatePolicyMutation = graphql`
status status
version version
reviewDate reviewDate
owner {
id
fullName
}
} }
} }
} }
@@ -62,12 +74,15 @@ function UpdatePolicyPageContent({
queryRef queryRef
); );
console.log("UpdatePolicyPage data:", data.node); console.log("UpdatePolicyPage data:", data.policy);
const [name, setName] = useState(data.node.name); const [name, setName] = useState(data.policy.name);
const [content, setContent] = useState(data.node.content || ""); const [content, setContent] = useState(data.policy.content || "");
const [status, setStatus] = useState(data.node.status); const [status, setStatus] = useState(data.policy.status);
const [reviewDate, setReviewDate] = useState(data.node.reviewDate || ""); const [reviewDate, setReviewDate] = useState(data.policy.reviewDate || "");
const [ownerId, setOwnerId] = useState<string | null>(
data.policy.owner?.id || null
);
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
console.log( console.log(
@@ -93,12 +108,13 @@ function UpdatePolicyPageContent({
commitMutation({ commitMutation({
variables: { variables: {
input: { input: {
id: data.node.id, id: data.policy.id,
name, name,
content, content,
status, status,
reviewDate: reviewDateValue, reviewDate: reviewDateValue,
expectedVersion: data.node.version!, ownerId,
expectedVersion: data.policy.version!,
}, },
}, },
onCompleted: (response, errors) => { onCompleted: (response, errors) => {
@@ -200,6 +216,20 @@ function UpdatePolicyPageContent({
</RadioGroup> </RadioGroup>
</div> </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"> <div className="space-y-2">
<Label <Label
htmlFor="reviewDate" htmlFor="reviewDate"
@@ -253,48 +283,26 @@ function UpdatePolicyPageFallback() {
<div className="h-4 w-64 bg-muted animate-pulse rounded" /> <div className="h-4 w-64 bg-muted animate-pulse rounded" />
</div> </div>
</div> </div>
<div className="bg-muted animate-pulse rounded-lg h-[600px]" />
<div className="grid gap-6">
<Card>
<CardHeader>
<div className="h-6 w-32 bg-muted animate-pulse rounded" />
</CardHeader>
<CardContent className="space-y-4">
{[1, 2, 3, 4].map((i) => (
<div key={i} className="space-y-2">
<div className="h-4 w-24 bg-muted animate-pulse rounded" />
<div className="h-10 w-full bg-muted animate-pulse rounded" />
</div>
))}
</CardContent>
</Card>
</div>
</div> </div>
); );
} }
export default function UpdatePolicyPage() { export default function UpdatePolicyPage() {
const { organizationId, policyId } = useParams();
const [queryRef, loadQuery] = useQueryLoader<UpdatePolicyPageQueryType>( const [queryRef, loadQuery] = useQueryLoader<UpdatePolicyPageQueryType>(
UpdatePolicyPageQuery UpdatePolicyPageQuery
); );
const { policyId } = useParams();
useEffect(() => { useEffect(() => {
loadQuery({ policyId: policyId! }); if (organizationId && policyId) {
}, [loadQuery, policyId]); loadQuery({ organizationId, policyId });
if (!queryRef) {
return <UpdatePolicyPageFallback />;
} }
}, [organizationId, policyId, loadQuery]);
return ( return (
<>
<Helmet>
<title>Update Policy - Probo Console</title>
</Helmet>
<Suspense fallback={<UpdatePolicyPageFallback />}> <Suspense fallback={<UpdatePolicyPageFallback />}>
<UpdatePolicyPageContent queryRef={queryRef} /> {queryRef && <UpdatePolicyPageContent queryRef={queryRef} />}
</Suspense> </Suspense>
</>
); );
} }

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<d242fdb748915009dc461be5596b6fc1>> * @generated SignedSource<<29caed5a50be912ff48f9c131ce4045b>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -14,6 +14,7 @@ export type CreatePolicyInput = {
content: string; content: string;
name: string; name: string;
organizationId: string; organizationId: string;
ownerId: string;
reviewDate?: string | null | undefined; reviewDate?: string | null | undefined;
status: PolicyStatus; status: PolicyStatus;
}; };
@@ -28,6 +29,10 @@ export type CreatePolicyPageMutation$data = {
readonly content: string; readonly content: string;
readonly id: string; readonly id: string;
readonly name: string; readonly name: string;
readonly owner: {
readonly fullName: string;
readonly id: string;
};
readonly reviewDate: string | null | undefined; readonly reviewDate: string | null | undefined;
readonly status: PolicyStatus; readonly status: PolicyStatus;
}; };
@@ -58,6 +63,13 @@ v2 = [
} }
], ],
v3 = { v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v4 = {
"alias": null, "alias": null,
"args": null, "args": null,
"concreteType": "PolicyEdge", "concreteType": "PolicyEdge",
@@ -73,13 +85,7 @@ v3 = {
"name": "node", "name": "node",
"plural": false, "plural": false,
"selections": [ "selections": [
{ (v3/*: any*/),
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -107,6 +113,25 @@ v3 = {
"kind": "ScalarField", "kind": "ScalarField",
"name": "reviewDate", "name": "reviewDate",
"storageKey": null "storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "owner",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
}
],
"storageKey": null
} }
], ],
"storageKey": null "storageKey": null
@@ -132,7 +157,7 @@ return {
"name": "createPolicy", "name": "createPolicy",
"plural": false, "plural": false,
"selections": [ "selections": [
(v3/*: any*/) (v4/*: any*/)
], ],
"storageKey": null "storageKey": null
} }
@@ -157,7 +182,7 @@ return {
"name": "createPolicy", "name": "createPolicy",
"plural": false, "plural": false,
"selections": [ "selections": [
(v3/*: any*/), (v4/*: any*/),
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -180,16 +205,16 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "64796b84575bf926f45da4c98d31e645", "cacheID": "cf17928879d0f2b2e68367ed2c132d0a",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "CreatePolicyPageMutation", "name": "CreatePolicyPageMutation",
"operationKind": "mutation", "operationKind": "mutation",
"text": "mutation CreatePolicyPageMutation(\n $input: CreatePolicyInput!\n) {\n createPolicy(input: $input) {\n policyEdge {\n node {\n id\n name\n content\n status\n reviewDate\n }\n }\n }\n}\n" "text": "mutation CreatePolicyPageMutation(\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"
} }
}; };
})(); })();
(node as any).hash = "b7ecb61ced1e31c0c0093946135eca3b"; (node as any).hash = "1ddb9b7010ecf1fe55e2b56aaa034295";
export default node; export default node;

View File

@@ -0,0 +1,176 @@
/**
* @generated SignedSource<<0229a3e8e814cf3971479aa5abc20322>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type CreatePolicyPageQuery$variables = {
organizationId: string;
};
export type CreatePolicyPageQuery$data = {
readonly organization: {
readonly " $fragmentSpreads": FragmentRefs<"PeopleSelector_organization">;
};
};
export type CreatePolicyPageQuery = {
response: CreatePolicyPageQuery$data;
variables: CreatePolicyPageQuery$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
};
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "CreatePolicyPageQuery",
"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": "CreatePolicyPageQuery",
"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": [
{
"alias": null,
"args": [
{
"kind": "Literal",
"name": "first",
"value": 100
}
],
"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
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "peoples(first:100)"
}
],
"type": "Organization",
"abstractKey": null
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "edb18dbe0439fa7ca7bc791e607cd447",
"id": null,
"metadata": {},
"name": "CreatePolicyPageQuery",
"operationKind": "query",
"text": "query CreatePolicyPageQuery(\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) {\n edges {\n node {\n id\n fullName\n primaryEmailAddress\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "9ab69c4bfcba5c896760f297ab38ff51";
export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<ebffaaa37d8815b22f9fa75aca6e75d2>> * @generated SignedSource<<a49c4b1ca92e109b1091a1431fe0c871>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -19,11 +19,11 @@ export type PolicyListPageQuery$data = {
readonly edges: ReadonlyArray<{ readonly edges: ReadonlyArray<{
readonly node: { readonly node: {
readonly content: string; readonly content: string;
readonly createdAt: any; readonly createdAt: string;
readonly id: string; readonly id: string;
readonly name: string; readonly name: string;
readonly status: PolicyStatus; readonly status: PolicyStatus;
readonly updatedAt: any; readonly updatedAt: string;
}; };
}>; }>;
}; };

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<0eb945d3cbf8f223621fe7aa2b93eddc>> * @generated SignedSource<<67ee63bc3751efdd9ecdf2ad9ab5e197>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -19,6 +19,11 @@ export type PolicyOverviewPageQuery$data = {
readonly createdAt?: string; readonly createdAt?: string;
readonly id: string; readonly id: string;
readonly name?: string; readonly name?: string;
readonly owner?: {
readonly fullName: string;
readonly id: string;
readonly primaryEmailAddress: string;
};
readonly reviewDate?: string | null | undefined; readonly reviewDate?: string | null | undefined;
readonly status?: PolicyStatus; readonly status?: PolicyStatus;
readonly updatedAt?: string; readonly updatedAt?: string;
@@ -95,6 +100,32 @@ v3 = {
"kind": "ScalarField", "kind": "ScalarField",
"name": "status", "name": "status",
"storageKey": null "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", "type": "Policy",
@@ -153,16 +184,16 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "d3b31142132a379f9c3a8812ddcfb7da", "cacheID": "c57fd1a2ff5ffa776bebb9f15bd534bc",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "PolicyOverviewPageQuery", "name": "PolicyOverviewPageQuery",
"operationKind": "query", "operationKind": "query",
"text": "query PolicyOverviewPageQuery(\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 }\n }\n}\n" "text": "query PolicyOverviewPageQuery(\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 = "1519b589fe7da5a258621ad445970ca7"; (node as any).hash = "e6c4f286358df61d18ea6deb367d7182";
export default node; export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<9a54ece29b9ff9a2efa624f67f40f803>> * @generated SignedSource<<f37225ac5a4b6774dadbd4f23c3bbfe9>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -15,6 +15,7 @@ export type UpdatePolicyInput = {
expectedVersion: number; expectedVersion: number;
id: string; id: string;
name?: string | null | undefined; name?: string | null | undefined;
ownerId?: string | null | undefined;
reviewDate?: string | null | undefined; reviewDate?: string | null | undefined;
status?: PolicyStatus | null | undefined; status?: PolicyStatus | null | undefined;
}; };
@@ -27,6 +28,10 @@ export type UpdatePolicyPageMutation$data = {
readonly content: string; readonly content: string;
readonly id: string; readonly id: string;
readonly name: string; readonly name: string;
readonly owner: {
readonly fullName: string;
readonly id: string;
};
readonly reviewDate: string | null | undefined; readonly reviewDate: string | null | undefined;
readonly status: PolicyStatus; readonly status: PolicyStatus;
readonly version: number; readonly version: number;
@@ -46,7 +51,14 @@ var v0 = [
"name": "input" "name": "input"
} }
], ],
v1 = [ v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v2 = [
{ {
"alias": null, "alias": null,
"args": [ "args": [
@@ -69,13 +81,7 @@ v1 = [
"name": "policy", "name": "policy",
"plural": false, "plural": false,
"selections": [ "selections": [
{ (v1/*: any*/),
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -110,6 +116,25 @@ v1 = [
"kind": "ScalarField", "kind": "ScalarField",
"name": "reviewDate", "name": "reviewDate",
"storageKey": null "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 "storageKey": null
@@ -124,7 +149,7 @@ return {
"kind": "Fragment", "kind": "Fragment",
"metadata": null, "metadata": null,
"name": "UpdatePolicyPageMutation", "name": "UpdatePolicyPageMutation",
"selections": (v1/*: any*/), "selections": (v2/*: any*/),
"type": "Mutation", "type": "Mutation",
"abstractKey": null "abstractKey": null
}, },
@@ -133,19 +158,19 @@ return {
"argumentDefinitions": (v0/*: any*/), "argumentDefinitions": (v0/*: any*/),
"kind": "Operation", "kind": "Operation",
"name": "UpdatePolicyPageMutation", "name": "UpdatePolicyPageMutation",
"selections": (v1/*: any*/) "selections": (v2/*: any*/)
}, },
"params": { "params": {
"cacheID": "ce2ccf9449b52e3df3b2b9bb82c55166", "cacheID": "cab3ba1fb88944fade60b5d6a0e0b7b9",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "UpdatePolicyPageMutation", "name": "UpdatePolicyPageMutation",
"operationKind": "mutation", "operationKind": "mutation",
"text": "mutation UpdatePolicyPageMutation(\n $input: UpdatePolicyInput!\n) {\n updatePolicy(input: $input) {\n policy {\n id\n name\n content\n status\n version\n reviewDate\n }\n }\n}\n" "text": "mutation UpdatePolicyPageMutation(\n $input: UpdatePolicyInput!\n) {\n updatePolicy(input: $input) {\n policy {\n id\n name\n content\n status\n version\n reviewDate\n owner {\n id\n fullName\n }\n }\n }\n}\n"
} }
}; };
})(); })();
(node as any).hash = "d5329966d0e13b8931d6f01d9afb74e2"; (node as any).hash = "51e4a0e297540f4e83dcb6d809430ac7";
export default node; export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<5ee387b238e8fdc7c41851c7e6740c95>> * @generated SignedSource<<04e2a37d31d4cd204101efdd85471dc5>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -9,15 +9,24 @@
// @ts-nocheck // @ts-nocheck
import { ConcreteRequest } from 'relay-runtime'; import { ConcreteRequest } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type PolicyStatus = "ACTIVE" | "DRAFT"; export type PolicyStatus = "ACTIVE" | "DRAFT";
export type UpdatePolicyPageQuery$variables = { export type UpdatePolicyPageQuery$variables = {
organizationId: string;
policyId: string; policyId: string;
}; };
export type UpdatePolicyPageQuery$data = { export type UpdatePolicyPageQuery$data = {
readonly node: { readonly organization: {
readonly " $fragmentSpreads": FragmentRefs<"PeopleSelector_organization">;
};
readonly policy: {
readonly content?: string; readonly content?: string;
readonly id: string; readonly id: string;
readonly name?: string; readonly name?: string;
readonly owner?: {
readonly fullName: string;
readonly id: string;
};
readonly reviewDate?: string | null | undefined; readonly reviewDate?: string | null | undefined;
readonly status?: PolicyStatus; readonly status?: PolicyStatus;
readonly version?: number; readonly version?: number;
@@ -29,28 +38,38 @@ export type UpdatePolicyPageQuery = {
}; };
const node: ConcreteRequest = (function(){ const node: ConcreteRequest = (function(){
var v0 = [ var v0 = {
{ "defaultValue": null,
"kind": "LocalArgument",
"name": "organizationId"
},
v1 = {
"defaultValue": null, "defaultValue": null,
"kind": "LocalArgument", "kind": "LocalArgument",
"name": "policyId" "name": "policyId"
} },
], v2 = [
v1 = [
{ {
"kind": "Variable", "kind": "Variable",
"name": "id", "name": "id",
"variableName": "policyId" "variableName": "policyId"
} }
], ],
v2 = { v3 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "id", "name": "id",
"storageKey": null "storageKey": null
}, },
v3 = { v4 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
},
v5 = {
"kind": "InlineFragment", "kind": "InlineFragment",
"selections": [ "selections": [
{ {
@@ -87,28 +106,74 @@ v3 = {
"kind": "ScalarField", "kind": "ScalarField",
"name": "reviewDate", "name": "reviewDate",
"storageKey": null "storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "owner",
"plural": false,
"selections": [
(v3/*: any*/),
(v4/*: any*/)
],
"storageKey": null
} }
], ],
"type": "Policy", "type": "Policy",
"abstractKey": null "abstractKey": null
},
v6 = [
{
"kind": "Variable",
"name": "id",
"variableName": "organizationId"
}
],
v7 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
}; };
return { return {
"fragment": { "fragment": {
"argumentDefinitions": (v0/*: any*/), "argumentDefinitions": [
(v0/*: any*/),
(v1/*: any*/)
],
"kind": "Fragment", "kind": "Fragment",
"metadata": null, "metadata": null,
"name": "UpdatePolicyPageQuery", "name": "UpdatePolicyPageQuery",
"selections": [ "selections": [
{ {
"alias": null, "alias": "policy",
"args": (v1/*: any*/), "args": (v2/*: any*/),
"concreteType": null, "concreteType": null,
"kind": "LinkedField", "kind": "LinkedField",
"name": "node", "name": "node",
"plural": false, "plural": false,
"selections": [ "selections": [
(v2/*: any*/), (v3/*: any*/),
(v3/*: any*/) (v5/*: any*/)
],
"storageKey": null
},
{
"alias": "organization",
"args": (v6/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"args": null,
"kind": "FragmentSpread",
"name": "PeopleSelector_organization"
}
], ],
"storageKey": null "storageKey": null
} }
@@ -118,43 +183,108 @@ return {
}, },
"kind": "Request", "kind": "Request",
"operation": { "operation": {
"argumentDefinitions": (v0/*: any*/), "argumentDefinitions": [
(v1/*: any*/),
(v0/*: any*/)
],
"kind": "Operation", "kind": "Operation",
"name": "UpdatePolicyPageQuery", "name": "UpdatePolicyPageQuery",
"selections": [ "selections": [
{ {
"alias": null, "alias": "policy",
"args": (v1/*: any*/), "args": (v2/*: any*/),
"concreteType": null, "concreteType": null,
"kind": "LinkedField", "kind": "LinkedField",
"name": "node", "name": "node",
"plural": false, "plural": false,
"selections": [ "selections": [
(v7/*: any*/),
(v3/*: any*/),
(v5/*: any*/)
],
"storageKey": null
},
{
"alias": "organization",
"args": (v6/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v7/*: any*/),
(v3/*: any*/),
{
"kind": "InlineFragment",
"selections": [
{
"alias": null,
"args": [
{
"kind": "Literal",
"name": "first",
"value": 100
}
],
"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*/),
(v4/*: any*/),
{ {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "__typename", "name": "primaryEmailAddress",
"storageKey": null "storageKey": null
}, }
(v2/*: any*/), ],
(v3/*: any*/) "storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "peoples(first:100)"
}
],
"type": "Organization",
"abstractKey": null
}
], ],
"storageKey": null "storageKey": null
} }
] ]
}, },
"params": { "params": {
"cacheID": "fc062f2d2fcce04954feb69436433093", "cacheID": "abbd1265e5fbacc34d5442772cbd645b",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "UpdatePolicyPageQuery", "name": "UpdatePolicyPageQuery",
"operationKind": "query", "operationKind": "query",
"text": "query UpdatePolicyPageQuery(\n $policyId: ID!\n) {\n node(id: $policyId) {\n __typename\n id\n ... on Policy {\n name\n content\n status\n version\n reviewDate\n }\n }\n}\n" "text": "query UpdatePolicyPageQuery(\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 version\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) {\n edges {\n node {\n id\n fullName\n primaryEmailAddress\n }\n }\n }\n}\n"
} }
}; };
})(); })();
(node as any).hash = "4ae49cad4f03a09d87475483c271bd94"; (node as any).hash = "abba76ee5208701e443130d650b5c8d1";
export default node; export default node;

View File

@@ -650,6 +650,7 @@ input CreatePolicyInput {
content: String! content: String!
status: PolicyStatus! status: PolicyStatus!
reviewDate: Datetime reviewDate: Datetime
ownerId: ID!
} }
input UpdatePolicyInput { input UpdatePolicyInput {
@@ -659,6 +660,7 @@ input UpdatePolicyInput {
content: String content: String
status: PolicyStatus status: PolicyStatus
reviewDate: Datetime reviewDate: Datetime
ownerId: ID
} }
input DeletePolicyInput { input DeletePolicyInput {
@@ -684,6 +686,7 @@ type Policy implements Node {
status: PolicyStatus! status: PolicyStatus!
content: String! content: String!
reviewDate: Datetime reviewDate: Datetime
owner: People! @goField(forceResolver: true)
createdAt: Datetime! createdAt: Datetime!
updatedAt: Datetime! updatedAt: Datetime!
} }

View File

@@ -47,6 +47,7 @@ type ResolverRoot interface {
Framework() FrameworkResolver Framework() FrameworkResolver
Mutation() MutationResolver Mutation() MutationResolver
Organization() OrganizationResolver Organization() OrganizationResolver
Policy() PolicyResolver
Query() QueryResolver Query() QueryResolver
Task() TaskResolver Task() TaskResolver
User() UserResolver User() UserResolver
@@ -289,6 +290,7 @@ type ComplexityRoot struct {
CreatedAt func(childComplexity int) int CreatedAt func(childComplexity int) int
ID func(childComplexity int) int ID func(childComplexity int) int
Name func(childComplexity int) int Name func(childComplexity int) int
Owner func(childComplexity int) int
ReviewDate func(childComplexity int) int ReviewDate func(childComplexity int) int
Status func(childComplexity int) int Status func(childComplexity int) int
UpdatedAt func(childComplexity int) int UpdatedAt func(childComplexity int) int
@@ -459,6 +461,9 @@ type OrganizationResolver interface {
Peoples(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.PeopleConnection, error) Peoples(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.PeopleConnection, error)
Policies(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.PolicyConnection, error) Policies(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.PolicyConnection, error)
} }
type PolicyResolver interface {
Owner(ctx context.Context, obj *types.Policy) (*types.People, error)
}
type QueryResolver interface { type QueryResolver interface {
Node(ctx context.Context, id gid.GID) (types.Node, error) Node(ctx context.Context, id gid.GID) (types.Node, error)
Viewer(ctx context.Context) (*types.User, error) Viewer(ctx context.Context) (*types.User, error)
@@ -1498,6 +1503,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Policy.Name(childComplexity), true return e.complexity.Policy.Name(childComplexity), true
case "Policy.owner":
if e.complexity.Policy.Owner == nil {
break
}
return e.complexity.Policy.Owner(childComplexity), true
case "Policy.reviewDate": case "Policy.reviewDate":
if e.complexity.Policy.ReviewDate == nil { if e.complexity.Policy.ReviewDate == nil {
break break
@@ -2743,6 +2755,7 @@ input CreatePolicyInput {
content: String! content: String!
status: PolicyStatus! status: PolicyStatus!
reviewDate: Datetime reviewDate: Datetime
ownerId: ID!
} }
input UpdatePolicyInput { input UpdatePolicyInput {
@@ -2752,6 +2765,7 @@ input UpdatePolicyInput {
content: String content: String
status: PolicyStatus status: PolicyStatus
reviewDate: Datetime reviewDate: Datetime
ownerId: ID
} }
input DeletePolicyInput { input DeletePolicyInput {
@@ -2777,6 +2791,7 @@ type Policy implements Node {
status: PolicyStatus! status: PolicyStatus!
content: String! content: String!
reviewDate: Datetime reviewDate: Datetime
owner: People! @goField(forceResolver: true)
createdAt: Datetime! createdAt: Datetime!
updatedAt: Datetime! updatedAt: Datetime!
} }
@@ -9518,6 +9533,62 @@ func (ec *executionContext) fieldContext_Policy_reviewDate(_ context.Context, fi
return fc, nil return fc, nil
} }
func (ec *executionContext) _Policy_owner(ctx context.Context, field graphql.CollectedField, obj *types.Policy) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Policy_owner(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Policy().Owner(rctx, obj)
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*types.People)
fc.Result = res
return ec.marshalNPeople2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐPeople(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_Policy_owner(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Policy",
Field: field,
IsMethod: true,
IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "id":
return ec.fieldContext_People_id(ctx, field)
case "fullName":
return ec.fieldContext_People_fullName(ctx, field)
case "primaryEmailAddress":
return ec.fieldContext_People_primaryEmailAddress(ctx, field)
case "additionalEmailAddresses":
return ec.fieldContext_People_additionalEmailAddresses(ctx, field)
case "kind":
return ec.fieldContext_People_kind(ctx, field)
case "createdAt":
return ec.fieldContext_People_createdAt(ctx, field)
case "updatedAt":
return ec.fieldContext_People_updatedAt(ctx, field)
case "version":
return ec.fieldContext_People_version(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type People", field.Name)
},
}
return fc, nil
}
func (ec *executionContext) _Policy_createdAt(ctx context.Context, field graphql.CollectedField, obj *types.Policy) (ret graphql.Marshaler) { func (ec *executionContext) _Policy_createdAt(ctx context.Context, field graphql.CollectedField, obj *types.Policy) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Policy_createdAt(ctx, field) fc, err := ec.fieldContext_Policy_createdAt(ctx, field)
if err != nil { if err != nil {
@@ -9769,6 +9840,8 @@ func (ec *executionContext) fieldContext_PolicyEdge_node(_ context.Context, fiel
return ec.fieldContext_Policy_content(ctx, field) return ec.fieldContext_Policy_content(ctx, field)
case "reviewDate": case "reviewDate":
return ec.fieldContext_Policy_reviewDate(ctx, field) return ec.fieldContext_Policy_reviewDate(ctx, field)
case "owner":
return ec.fieldContext_Policy_owner(ctx, field)
case "createdAt": case "createdAt":
return ec.fieldContext_Policy_createdAt(ctx, field) return ec.fieldContext_Policy_createdAt(ctx, field)
case "updatedAt": case "updatedAt":
@@ -11193,6 +11266,8 @@ func (ec *executionContext) fieldContext_UpdatePolicyPayload_policy(_ context.Co
return ec.fieldContext_Policy_content(ctx, field) return ec.fieldContext_Policy_content(ctx, field)
case "reviewDate": case "reviewDate":
return ec.fieldContext_Policy_reviewDate(ctx, field) return ec.fieldContext_Policy_reviewDate(ctx, field)
case "owner":
return ec.fieldContext_Policy_owner(ctx, field)
case "createdAt": case "createdAt":
return ec.fieldContext_Policy_createdAt(ctx, field) return ec.fieldContext_Policy_createdAt(ctx, field)
case "updatedAt": case "updatedAt":
@@ -14016,7 +14091,7 @@ func (ec *executionContext) unmarshalInputCreatePolicyInput(ctx context.Context,
asMap[k] = v asMap[k] = v
} }
fieldsInOrder := [...]string{"organizationId", "name", "content", "status", "reviewDate"} fieldsInOrder := [...]string{"organizationId", "name", "content", "status", "reviewDate", "ownerId"}
for _, k := range fieldsInOrder { for _, k := range fieldsInOrder {
v, ok := asMap[k] v, ok := asMap[k]
if !ok { if !ok {
@@ -14058,6 +14133,13 @@ func (ec *executionContext) unmarshalInputCreatePolicyInput(ctx context.Context,
return it, err return it, err
} }
it.ReviewDate = data it.ReviewDate = data
case "ownerId":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("ownerId"))
data, err := ec.unmarshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGID(ctx, v)
if err != nil {
return it, err
}
it.OwnerID = data
} }
} }
@@ -14536,7 +14618,7 @@ func (ec *executionContext) unmarshalInputUpdatePolicyInput(ctx context.Context,
asMap[k] = v asMap[k] = v
} }
fieldsInOrder := [...]string{"id", "expectedVersion", "name", "content", "status", "reviewDate"} fieldsInOrder := [...]string{"id", "expectedVersion", "name", "content", "status", "reviewDate", "ownerId"}
for _, k := range fieldsInOrder { for _, k := range fieldsInOrder {
v, ok := asMap[k] v, ok := asMap[k]
if !ok { if !ok {
@@ -14585,6 +14667,13 @@ func (ec *executionContext) unmarshalInputUpdatePolicyInput(ctx context.Context,
return it, err return it, err
} }
it.ReviewDate = data it.ReviewDate = data
case "ownerId":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("ownerId"))
data, err := ec.unmarshalOID2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGID(ctx, v)
if err != nil {
return it, err
}
it.OwnerID = data
} }
} }
@@ -16945,39 +17034,70 @@ func (ec *executionContext) _Policy(ctx context.Context, sel ast.SelectionSet, o
case "id": case "id":
out.Values[i] = ec._Policy_id(ctx, field, obj) out.Values[i] = ec._Policy_id(ctx, field, obj)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
out.Invalids++ atomic.AddUint32(&out.Invalids, 1)
} }
case "version": case "version":
out.Values[i] = ec._Policy_version(ctx, field, obj) out.Values[i] = ec._Policy_version(ctx, field, obj)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
out.Invalids++ atomic.AddUint32(&out.Invalids, 1)
} }
case "name": case "name":
out.Values[i] = ec._Policy_name(ctx, field, obj) out.Values[i] = ec._Policy_name(ctx, field, obj)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
out.Invalids++ atomic.AddUint32(&out.Invalids, 1)
} }
case "status": case "status":
out.Values[i] = ec._Policy_status(ctx, field, obj) out.Values[i] = ec._Policy_status(ctx, field, obj)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
out.Invalids++ atomic.AddUint32(&out.Invalids, 1)
} }
case "content": case "content":
out.Values[i] = ec._Policy_content(ctx, field, obj) out.Values[i] = ec._Policy_content(ctx, field, obj)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
out.Invalids++ atomic.AddUint32(&out.Invalids, 1)
} }
case "reviewDate": case "reviewDate":
out.Values[i] = ec._Policy_reviewDate(ctx, field, obj) out.Values[i] = ec._Policy_reviewDate(ctx, field, obj)
case "owner":
field := field
innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) {
res = ec._Policy_owner(ctx, field, obj)
if res == graphql.Null {
atomic.AddUint32(&fs.Invalids, 1)
}
return res
}
if field.Deferrable != nil {
dfs, ok := deferred[field.Deferrable.Label]
di := 0
if ok {
dfs.AddField(field)
di = len(dfs.Values) - 1
} else {
dfs = graphql.NewFieldSet([]graphql.CollectedField{field})
deferred[field.Deferrable.Label] = dfs
}
dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler {
return innerFunc(ctx, dfs)
})
// don't run the out.Concurrently() call below
out.Values[i] = graphql.Null
continue
}
out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
case "createdAt": case "createdAt":
out.Values[i] = ec._Policy_createdAt(ctx, field, obj) out.Values[i] = ec._Policy_createdAt(ctx, field, obj)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
out.Invalids++ atomic.AddUint32(&out.Invalids, 1)
} }
case "updatedAt": case "updatedAt":
out.Values[i] = ec._Policy_updatedAt(ctx, field, obj) out.Values[i] = ec._Policy_updatedAt(ctx, field, obj)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
out.Invalids++ atomic.AddUint32(&out.Invalids, 1)
} }
default: default:
panic("unknown field " + strconv.Quote(field.Name)) panic("unknown field " + strconv.Quote(field.Name))
@@ -19276,6 +19396,10 @@ func (ec *executionContext) marshalNPageInfo2ᚖgithubᚗcomᚋgetproboᚋprobo
return ec._PageInfo(ctx, sel, v) return ec._PageInfo(ctx, sel, v)
} }
func (ec *executionContext) marshalNPeople2githubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐPeople(ctx context.Context, sel ast.SelectionSet, v types.People) graphql.Marshaler {
return ec._People(ctx, sel, &v)
}
func (ec *executionContext) marshalNPeople2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐPeople(ctx context.Context, sel ast.SelectionSet, v *types.People) graphql.Marshaler { func (ec *executionContext) marshalNPeople2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐPeople(ctx context.Context, sel ast.SelectionSet, v *types.People) graphql.Marshaler {
if v == nil { if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
@@ -20333,6 +20457,22 @@ var (
} }
) )
func (ec *executionContext) unmarshalOID2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGID(ctx context.Context, v any) (*gid.GID, error) {
if v == nil {
return nil, nil
}
res, err := types.UnmarshalGIDScalar(v)
return &res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) marshalOID2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGID(ctx context.Context, sel ast.SelectionSet, v *gid.GID) graphql.Marshaler {
if v == nil {
return graphql.Null
}
res := types.MarshalGIDScalar(*v)
return res
}
func (ec *executionContext) unmarshalOInt2ᚖint(ctx context.Context, v any) (*int, error) { func (ec *executionContext) unmarshalOInt2ᚖint(ctx context.Context, v any) (*int, error) {
if v == nil { if v == nil {
return nil, nil return nil, nil

View File

@@ -108,6 +108,7 @@ type CreatePolicyInput struct {
Content string `json:"content"` Content string `json:"content"`
Status coredata.PolicyStatus `json:"status"` Status coredata.PolicyStatus `json:"status"`
ReviewDate *time.Time `json:"reviewDate,omitempty"` ReviewDate *time.Time `json:"reviewDate,omitempty"`
OwnerID gid.GID `json:"ownerId"`
} }
type CreatePolicyPayload struct { type CreatePolicyPayload struct {
@@ -322,6 +323,7 @@ type Policy struct {
Status coredata.PolicyStatus `json:"status"` Status coredata.PolicyStatus `json:"status"`
Content string `json:"content"` Content string `json:"content"`
ReviewDate *time.Time `json:"reviewDate,omitempty"` ReviewDate *time.Time `json:"reviewDate,omitempty"`
Owner *People `json:"owner"`
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"` UpdatedAt time.Time `json:"updatedAt"`
} }
@@ -434,6 +436,7 @@ type UpdatePolicyInput struct {
Content *string `json:"content,omitempty"` Content *string `json:"content,omitempty"`
Status *coredata.PolicyStatus `json:"status,omitempty"` Status *coredata.PolicyStatus `json:"status,omitempty"`
ReviewDate *time.Time `json:"reviewDate,omitempty"` ReviewDate *time.Time `json:"reviewDate,omitempty"`
OwnerID *gid.GID `json:"ownerId,omitempty"`
} }
type UpdatePolicyPayload struct { type UpdatePolicyPayload struct {

View File

@@ -383,6 +383,7 @@ func (r *mutationResolver) CreatePolicy(ctx context.Context, input types.CreateP
Content: input.Content, Content: input.Content,
Status: input.Status, Status: input.Status,
ReviewDate: input.ReviewDate, ReviewDate: input.ReviewDate,
OwnerID: input.OwnerID,
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot create policy: %w", err) return nil, fmt.Errorf("cannot create policy: %w", err)
@@ -402,6 +403,7 @@ func (r *mutationResolver) UpdatePolicy(ctx context.Context, input types.UpdateP
Content: input.Content, Content: input.Content,
Status: input.Status, Status: input.Status,
ReviewDate: input.ReviewDate, ReviewDate: input.ReviewDate,
OwnerID: input.OwnerID,
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot update policy: %w", err) return nil, fmt.Errorf("cannot update policy: %w", err)
@@ -472,6 +474,22 @@ func (r *organizationResolver) Policies(ctx context.Context, obj *types.Organiza
return types.NewPolicyConnection(page), nil return types.NewPolicyConnection(page), nil
} }
// Owner is the resolver for the owner field.
func (r *policyResolver) Owner(ctx context.Context, obj *types.Policy) (*types.People, error) {
policy, err := r.proboSvc.Policies.Get(ctx, obj.ID)
if err != nil {
return nil, fmt.Errorf("cannot get policy: %w", err)
}
// Get the owner
owner, err := r.proboSvc.GetPeople(ctx, policy.OwnerID)
if err != nil {
return nil, fmt.Errorf("cannot get owner: %w", err)
}
return types.NewPeople(owner), nil
}
// Node is the resolver for the node field. // Node is the resolver for the node field.
func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error) { func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error) {
switch id.EntityType() { switch id.EntityType() {
@@ -613,6 +631,9 @@ func (r *Resolver) Mutation() schema.MutationResolver { return &mutationResolver
// Organization returns schema.OrganizationResolver implementation. // Organization returns schema.OrganizationResolver implementation.
func (r *Resolver) Organization() schema.OrganizationResolver { return &organizationResolver{r} } func (r *Resolver) Organization() schema.OrganizationResolver { return &organizationResolver{r} }
// Policy returns schema.PolicyResolver implementation.
func (r *Resolver) Policy() schema.PolicyResolver { return &policyResolver{r} }
// Query returns schema.QueryResolver implementation. // Query returns schema.QueryResolver implementation.
func (r *Resolver) Query() schema.QueryResolver { return &queryResolver{r} } func (r *Resolver) Query() schema.QueryResolver { return &queryResolver{r} }
@@ -627,6 +648,7 @@ type evidenceResolver struct{ *Resolver }
type frameworkResolver struct{ *Resolver } type frameworkResolver struct{ *Resolver }
type mutationResolver struct{ *Resolver } type mutationResolver struct{ *Resolver }
type organizationResolver struct{ *Resolver } type organizationResolver struct{ *Resolver }
type policyResolver struct{ *Resolver }
type queryResolver struct{ *Resolver } type queryResolver struct{ *Resolver }
type taskResolver struct{ *Resolver } type taskResolver struct{ *Resolver }
type userResolver struct{ *Resolver } type userResolver struct{ *Resolver }

View File

@@ -0,0 +1 @@
ALTER TABLE policies ADD COLUMN owner_id TEXT REFERENCES peoples(id) NOT NULL;

View File

@@ -16,6 +16,7 @@ type (
Policy struct { Policy struct {
ID gid.GID `db:"id"` ID gid.GID `db:"id"`
OrganizationID gid.GID `db:"organization_id"` OrganizationID gid.GID `db:"organization_id"`
OwnerID gid.GID `db:"owner_id"`
Status PolicyStatus `db:"status"` Status PolicyStatus `db:"status"`
Name string `db:"name"` Name string `db:"name"`
Content string `db:"content"` Content string `db:"content"`
@@ -33,6 +34,7 @@ type (
Content *string Content *string
Status *PolicyStatus Status *PolicyStatus
ReviewDate **time.Time ReviewDate **time.Time
OwnerID *gid.GID
} }
) )
@@ -50,6 +52,7 @@ func (p *Policy) LoadByID(
SELECT SELECT
id, id,
organization_id, organization_id,
owner_id,
name, name,
status, status,
content, content,
@@ -96,6 +99,7 @@ func (p *Policies) LoadByOrganizationID(
SELECT SELECT
id, id,
organization_id, organization_id,
owner_id,
name, name,
status, status,
content, content,
@@ -141,6 +145,7 @@ INSERT INTO
policies ( policies (
id, id,
organization_id, organization_id,
owner_id,
name, name,
status, status,
content, content,
@@ -152,6 +157,7 @@ INSERT INTO
VALUES ( VALUES (
@policy_id, @policy_id,
@organization_id, @organization_id,
@owner_id,
@name, @name,
@status, @status,
@content, @content,
@@ -165,6 +171,7 @@ VALUES (
args := pgx.StrictNamedArgs{ args := pgx.StrictNamedArgs{
"policy_id": p.ID, "policy_id": p.ID,
"organization_id": p.OrganizationID, "organization_id": p.OrganizationID,
"owner_id": p.OwnerID,
"name": p.Name, "name": p.Name,
"status": p.Status, "status": p.Status,
"content": p.Content, "content": p.Content,
@@ -207,6 +214,7 @@ UPDATE policies SET
status = COALESCE(@status, status), status = COALESCE(@status, status),
content = COALESCE(@content, content), content = COALESCE(@content, content),
review_date = COALESCE(@review_date, review_date), review_date = COALESCE(@review_date, review_date),
owner_id = COALESCE(@owner_id, owner_id),
updated_at = @updated_at, updated_at = @updated_at,
version = version + 1 version = version + 1
WHERE %s WHERE %s
@@ -215,6 +223,7 @@ WHERE %s
RETURNING RETURNING
id, id,
organization_id, organization_id,
owner_id,
name, name,
content, content,
review_date, review_date,
@@ -243,6 +252,9 @@ RETURNING
if params.ReviewDate != nil { if params.ReviewDate != nil {
args["review_date"] = *params.ReviewDate args["review_date"] = *params.ReviewDate
} }
if params.OwnerID != nil {
args["owner_id"] = *params.OwnerID
}
maps.Copy(args, scope.SQLArguments()) maps.Copy(args, scope.SQLArguments())

View File

@@ -22,6 +22,7 @@ type (
Status coredata.PolicyStatus Status coredata.PolicyStatus
Content string Content string
ReviewDate *time.Time ReviewDate *time.Time
OwnerID gid.GID
} }
UpdatePolicyRequest struct { UpdatePolicyRequest struct {
@@ -31,6 +32,7 @@ type (
Content *string Content *string
Status *coredata.PolicyStatus Status *coredata.PolicyStatus
ReviewDate *time.Time ReviewDate *time.Time
OwnerID *gid.GID
} }
) )
@@ -68,6 +70,7 @@ func (s *PolicyService) Create(
policy := &coredata.Policy{ policy := &coredata.Policy{
ID: policyID, ID: policyID,
OrganizationID: req.OrganizationID, OrganizationID: req.OrganizationID,
OwnerID: req.OwnerID,
Name: req.Name, Name: req.Name,
Content: req.Content, Content: req.Content,
Status: req.Status, Status: req.Status,
@@ -108,6 +111,7 @@ func (s *PolicyService) Update(
Content: req.Content, Content: req.Content,
Status: req.Status, Status: req.Status,
ReviewDate: &req.ReviewDate, ReviewDate: &req.ReviewDate,
OwnerID: req.OwnerID,
} }
policy := &coredata.Policy{ID: req.ID} policy := &coredata.Policy{ID: req.ID}