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,205 +75,268 @@ function PolicyOverviewPageContent({
}; };
return ( return (
<div className="container mx-auto py-6"> <>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> <Helmet>
<div className="lg:col-span-2"> <title>{policy.name} - Probo Console</title>
<Card className="border shadow-sm"> </Helmet>
<CardContent className="p-6"> <div className="container mx-auto py-6">
<div className="flex items-center gap-3 mb-4"> <div className="flex justify-between items-start mb-6">
<div className="p-2 rounded-md bg-slate-100"> <div className="flex items-center">
<Shield className="h-6 w-6" /> <div className="mr-4">
</div> <div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10">
<div className="flex gap-2"> <FileText className="h-6 w-6 text-primary" />
<Badge </div>
variant="outline" </div>
className="bg-black text-white hover:bg-black/90 px-3 py-1 rounded-md font-medium" <div>
> <h1 className="text-2xl font-bold">{policy.name}</h1>
SOC2 <div className="flex items-center gap-2 text-muted-foreground">
</Badge> <Badge
<Badge className="px-3 py-1 rounded-md font-medium"> variant={policy.status === "ACTIVE" ? "default" : "outline"}
Security >
</Badge> {policy.status === "ACTIVE" ? "Active" : "Draft"}
{policy.status && ( </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="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">
<Badge <Badge
className={`px-3 py-1 rounded-md font-medium ${ variant="outline"
policy.status === "ACTIVE" className="bg-black text-white hover:bg-black/90 px-3 py-1 rounded-md font-medium"
? "bg-green-100 text-green-700 hover:bg-green-200" >
SOC2
</Badge>
<Badge className="px-3 py-1 rounded-md font-medium">
Security
</Badge>
{policy.status && (
<Badge
className={`px-3 py-1 rounded-md font-medium ${
policy.status === "ACTIVE"
? "bg-green-100 text-green-700 hover:bg-green-200"
: policy.status === "DRAFT"
? "bg-yellow-100 text-yellow-700 hover:bg-yellow-200"
: "bg-gray-100 text-gray-700 hover:bg-gray-200"
}`}
>
{policy.status === "ACTIVE"
? "Active"
: policy.status === "DRAFT" : policy.status === "DRAFT"
? "bg-yellow-100 text-yellow-700 hover:bg-yellow-200" ? "Draft"
: "bg-gray-100 text-gray-700 hover:bg-gray-200" : policy.status}
</Badge>
)}
</div>
</div>
<h1 className="text-3xl font-bold mb-3">{policy.name}</h1>
<p className="text-muted-foreground mb-6">
{getDescription(policy.content)}
</p>
<Tabs
defaultValue="content"
value={activeTab}
onValueChange={setActiveTab}
className="mb-6"
>
<TabsList className="border-b w-full rounded-none bg-transparent p-0 h-auto">
<TabsTrigger
value="content"
className={`rounded-none border-b-2 border-transparent px-4 py-2 font-medium ${
activeTab === "content"
? "border-primary text-primary"
: "text-muted-foreground"
}`} }`}
> >
{policy.status === "ACTIVE" Policy Content
? "Active" </TabsTrigger>
: policy.status === "DRAFT" <TabsTrigger
? "Draft" value="history"
: policy.status} className={`rounded-none border-b-2 border-transparent px-4 py-2 font-medium ${
</Badge> activeTab === "history"
)} ? "border-primary text-primary"
: "text-muted-foreground"
}`}
>
Version History
</TabsTrigger>
<TabsTrigger
value="approvals"
className={`rounded-none border-b-2 border-transparent px-4 py-2 font-medium ${
activeTab === "approvals"
? "border-primary text-primary"
: "text-muted-foreground"
}`}
>
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-muted-foreground">
Version history will be available soon.
</p>
</div>
</TabsContent>
<TabsContent value="approvals" className="pt-6">
<div className="text-center py-12">
<p className="text-muted-foreground">
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-muted-foreground 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-muted-foreground 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-muted-foreground mb-1">
<User className="h-4 w-4" />
<span className="text-sm">Owner</span>
</div>
<p className="font-medium">
{policy.owner ? policy.owner.fullName : "Not assigned"}
</p>
</div>
</div> </div>
<div className="ml-auto"> </CardContent>
<Button </Card>
variant="outline"
size="icon"
className="rounded-full"
>
<Download className="h-5 w-5" />
</Button>
</div>
</div>
<h1 className="text-3xl font-bold mb-3">{policy.name}</h1> <Button asChild className="w-full">
<Link
<p className="text-muted-foreground mb-6"> to={`/organizations/${organizationId}/policies/${policy.id}/update`}
{getDescription(policy.content)}
</p>
<Tabs
defaultValue="content"
value={activeTab}
onValueChange={setActiveTab}
className="mb-6"
> >
<TabsList className="border-b w-full rounded-none bg-transparent p-0 h-auto"> <Edit className="mr-2 h-4 w-4" />
<TabsTrigger Edit Policy
value="content" </Link>
className={`rounded-none border-b-2 border-transparent px-4 py-2 font-medium ${ </Button>
activeTab === "content"
? "border-primary text-primary" <Card className="border shadow-sm mt-6 bg-red-50">
: "text-muted-foreground" <CardContent className="p-6">
}`} <h3 className="text-red-500 font-semibold mb-3">Danger Zone</h3>
<p className="text-sm text-muted-foreground mb-4">
Permanently delete this policy and all of its data. This
action cannot be undone.
</p>
<Button variant="destructive" className="w-full">
<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"
> >
Policy Content <path d="M3 6h18"></path>
</TabsTrigger> <path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"></path>
<TabsTrigger <path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path>
value="history" </svg>
className={`rounded-none border-b-2 border-transparent px-4 py-2 font-medium ${ Delete Policy
activeTab === "history" </Button>
? "border-primary text-primary" </CardContent>
: "text-muted-foreground" </Card>
}`} </div>
>
Version History
</TabsTrigger>
<TabsTrigger
value="approvals"
className={`rounded-none border-b-2 border-transparent px-4 py-2 font-medium ${
activeTab === "approvals"
? "border-primary text-primary"
: "text-muted-foreground"
}`}
>
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-muted-foreground">
Version history will be available soon.
</p>
</div>
</TabsContent>
<TabsContent value="approvals" className="pt-6">
<div className="text-center py-12">
<p className="text-muted-foreground">
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-muted-foreground 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-muted-foreground 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-muted-foreground mb-1">
<User className="h-4 w-4" />
<span className="text-sm">Owner</span>
</div>
<p className="font-medium">Jane Smith, CISO</p>
</div>
</div>
</CardContent>
</Card>
<Button asChild className="w-full">
<Link
to={`/organizations/${organizationId}/policies/${policy.id}/update`}
>
<Edit className="mr-2 h-4 w-4" />
Edit Policy
</Link>
</Button>
<Card className="border shadow-sm mt-6 bg-red-50">
<CardContent className="p-6">
<h3 className="text-red-500 font-semibold mb-3">Danger Zone</h3>
<p className="text-sm text-muted-foreground mb-4">
Permanently delete this policy and all of its data. This action
cannot be undone.
</p>
<Button variant="destructive" className="w-full">
<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>
Delete Policy
</Button>
</CardContent>
</Card>
</div> </div>
</div> </div>
</div> </>
); );
} }
@@ -377,13 +445,8 @@ export default function PolicyOverviewPage() {
}, [loadQuery, policyId]); }, [loadQuery, policyId]);
return ( return (
<> <Suspense fallback={<PolicyOverviewPageFallback />}>
<Helmet> {queryRef && <PolicyOverviewPageContent queryRef={queryRef} />}
<title>Policy Details - Probo Console</title> </Suspense>
</Helmet>
<Suspense fallback={<PolicyOverviewPageFallback />}>
{queryRef && <PolicyOverviewPageContent queryRef={queryRef} />}
</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,8 +31,15 @@ const UpdatePolicyPageQuery = graphql`
status status
version version
reviewDate reviewDate
owner {
id
fullName
}
} }
} }
organization: node(id: $organizationId) {
...PeopleSelector_organization
}
} }
`; `;
@@ -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) { }, [organizationId, policyId, loadQuery]);
return <UpdatePolicyPageFallback />;
}
return ( return (
<> <Suspense fallback={<UpdatePolicyPageFallback />}>
<Helmet> {queryRef && <UpdatePolicyPageContent queryRef={queryRef} />}
<title>Update Policy - Probo Console</title> </Suspense>
</Helmet>
<Suspense fallback={<UpdatePolicyPageFallback />}>
<UpdatePolicyPageContent queryRef={queryRef} />
</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,
"defaultValue": null, "kind": "LocalArgument",
"kind": "LocalArgument", "name": "organizationId"
"name": "policyId" },
} v1 = {
], "defaultValue": null,
v1 = [ "kind": "LocalArgument",
"name": "policyId"
},
v2 = [
{ {
"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*/),
{ {
"alias": null, "kind": "InlineFragment",
"args": null, "selections": [
"kind": "ScalarField", {
"name": "__typename", "alias": null,
"storageKey": null "args": [
}, {
(v2/*: any*/), "kind": "Literal",
(v3/*: any*/) "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,
"args": null,
"kind": "ScalarField",
"name": "primaryEmailAddress",
"storageKey": null
}
],
"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}