Add task creation

Signed-off-by: gearnode <bryan@frimin.fr>
This commit is contained in:
gearnode
2025-02-26 15:47:57 +01:00
parent 58fddfc240
commit eaaab56968
11 changed files with 992 additions and 143 deletions

View File

@@ -0,0 +1,119 @@
import * as React from "react";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { X } from "lucide-react";
import { cn } from "@/lib/utils";
const Dialog = DialogPrimitive.Root;
const DialogTrigger = DialogPrimitive.Trigger;
const DialogPortal = DialogPrimitive.Portal;
const DialogClose = DialogPrimitive.Close;
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/50 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
/>
));
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-gray-200 bg-white p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-white transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-gray-950 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-gray-100 data-[state=open]:text-gray-500">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
));
DialogContent.displayName = DialogPrimitive.Content.displayName;
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left",
className
)}
{...props}
/>
);
DialogHeader.displayName = "DialogHeader";
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
);
DialogFooter.displayName = "DialogFooter";
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
));
DialogTitle.displayName = DialogPrimitive.Title.displayName;
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-gray-500", className)}
{...props}
/>
));
DialogDescription.displayName = DialogPrimitive.Description.displayName;
export {
Dialog,
DialogPortal,
DialogOverlay,
DialogClose,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
};

View File

@@ -1,4 +1,4 @@
import { Suspense, useEffect } from "react";
import { Suspense, useEffect, useState } from "react";
import { useParams } from "react-router";
import {
graphql,
@@ -7,13 +7,25 @@ import {
useQueryLoader,
useMutation,
} from "react-relay";
import { CheckCircle2 } from "lucide-react";
import { CheckCircle2, Plus } from "lucide-react";
import { Card, CardContent } from "@/components/ui/card";
import { useToast } from "@/hooks/use-toast";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Helmet } from "react-helmet-async";
import type { ControlOverviewPageQuery as ControlOverviewPageQueryType } from "./__generated__/ControlOverviewPageQuery.graphql";
import type { ControlOverviewPageUpdateTaskStateMutation as ControlOverviewPageUpdateTaskStateMutationType } from "./__generated__/ControlOverviewPageUpdateTaskStateMutation.graphql";
import type { ControlOverviewPageCreateTaskMutation as ControlOverviewPageCreateTaskMutationType } from "./__generated__/ControlOverviewPageCreateTaskMutation.graphql";
const controlOverviewPageQuery = graphql`
query ControlOverviewPageQuery($controlId: ID!) {
@@ -24,7 +36,8 @@ const controlOverviewPageQuery = graphql`
description
state
category
tasks {
tasks(first: 100) @connection(key: "ControlOverviewPage_tasks") {
__id
edges {
node {
id
@@ -44,9 +57,25 @@ const updateTaskStateMutation = graphql`
$input: UpdateTaskStateInput!
) {
updateTaskState(input: $input) {
taskEdge {
task {
id
state
}
}
}
`;
const createTaskMutation = graphql`
mutation ControlOverviewPageCreateTaskMutation(
$input: CreateTaskInput!
$connections: [ID!]!
) {
createTask(input: $input) {
taskEdge @prependEdge(connections: $connections) {
node {
id
name
description
state
}
}
@@ -68,9 +97,16 @@ function ControlOverviewPageContent({
useMutation<ControlOverviewPageUpdateTaskStateMutationType>(
updateTaskStateMutation
);
const [createTask] =
useMutation<ControlOverviewPageCreateTaskMutationType>(createTaskMutation);
const control = data.control;
const tasks = control?.tasks?.edges.map((edge) => edge?.node) ?? [];
// State for the create task dialog
const [isCreateTaskOpen, setIsCreateTaskOpen] = useState(false);
const [newTaskName, setNewTaskName] = useState("");
const [newTaskDescription, setNewTaskDescription] = useState("");
const handleTaskClick = (taskId: string, currentState: string) => {
const newState = currentState === "DONE" ? "TODO" : "DONE";
@@ -83,9 +119,11 @@ function ControlOverviewPageContent({
},
optimisticResponse: {
updateTaskState: {
task: {
id: taskId,
state: newState,
taskEdge: {
node: {
id: taskId,
state: newState,
},
},
},
},
@@ -107,6 +145,53 @@ function ControlOverviewPageContent({
});
};
const handleCreateTask = () => {
if (!newTaskName.trim()) {
toast({
title: "Error creating task",
description: "Task name is required",
variant: "destructive",
});
return;
}
if (!control?.id) {
toast({
title: "Error creating task",
description: "Control ID is missing",
variant: "destructive",
});
return;
}
createTask({
variables: {
connections: [`${data.control?.tasks?.__id}`],
input: {
controlId: control.id,
name: newTaskName,
description: newTaskDescription,
},
},
onCompleted: () => {
toast({
title: "Task created",
description: "New task has been created successfully.",
});
setNewTaskName("");
setNewTaskDescription("");
setIsCreateTaskOpen(false);
},
onError: (error) => {
toast({
title: "Error creating task",
description: error.message,
variant: "destructive",
});
},
});
};
return (
<div className="min-h-screen bg-white p-6 space-y-6">
<div className="space-y-4">
@@ -147,7 +232,59 @@ function ControlOverviewPageContent({
</Card>
<div>
<h2 className="text-xl font-semibold mb-6">Tasks</h2>
<div className="flex items-center justify-between mb-6">
<h2 className="text-xl font-semibold">Tasks</h2>
<Dialog open={isCreateTaskOpen} onOpenChange={setIsCreateTaskOpen}>
<DialogTrigger asChild>
<Button size="sm" className="flex items-center gap-1">
<Plus className="w-4 h-4" />
<span>Add Task</span>
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Create New Task</DialogTitle>
<DialogDescription>
Add a new task to this control. Click save when you&apos;re
done.
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="space-y-2">
<label htmlFor="name" className="text-sm font-medium">
Task Name
</label>
<Input
id="name"
value={newTaskName}
onChange={(e) => setNewTaskName(e.target.value)}
placeholder="Enter task name"
/>
</div>
<div className="space-y-2">
<label htmlFor="description" className="text-sm font-medium">
Description (optional)
</label>
<Input
id="description"
value={newTaskDescription}
onChange={(e) => setNewTaskDescription(e.target.value)}
placeholder="Enter task description"
/>
</div>
</div>
<DialogFooter>
<Button
variant="outline"
onClick={() => setIsCreateTaskOpen(false)}
>
Cancel
</Button>
<Button onClick={handleCreateTask}>Create Task</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
<div className="space-y-2">
{tasks.map((task) => (
<div
@@ -188,6 +325,17 @@ function ControlOverviewPageContent({
>
{task?.name}
</h3>
{task?.description && (
<p
className={`text-xs mt-1 ${
task?.state === "DONE"
? "text-gray-400 line-through"
: "text-gray-500"
}`}
>
{task.description}
</p>
)}
</div>
<div className="flex items-center gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
<div className="text-gray-400 text-sm">06.00 - 07.30</div>
@@ -206,6 +354,12 @@ function ControlOverviewPageContent({
</div>
</div>
))}
{tasks.length === 0 && (
<div className="text-center py-8 text-gray-500">
<p>No tasks yet. Click &quot;Add Task&quot; to create one.</p>
</div>
)}
</div>
</div>
</div>

View File

@@ -0,0 +1,185 @@
/**
* @generated SignedSource<<1454591ad65fc1aa76b9ac37604383ce>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type TaskState = "DONE" | "TODO";
export type CreateTaskInput = {
controlId: string;
description: string;
name: string;
};
export type ControlOverviewPageCreateTaskMutation$variables = {
connections: ReadonlyArray<string>;
input: CreateTaskInput;
};
export type ControlOverviewPageCreateTaskMutation$data = {
readonly createTask: {
readonly taskEdge: {
readonly node: {
readonly description: string;
readonly id: string;
readonly name: string;
readonly state: TaskState;
};
};
};
};
export type ControlOverviewPageCreateTaskMutation = {
response: ControlOverviewPageCreateTaskMutation$data;
variables: ControlOverviewPageCreateTaskMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "connections"
},
v1 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
},
v2 = [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
v3 = {
"alias": null,
"args": null,
"concreteType": "TaskEdge",
"kind": "LinkedField",
"name": "taskEdge",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Task",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "description",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "state",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": [
(v0/*: any*/),
(v1/*: any*/)
],
"kind": "Fragment",
"metadata": null,
"name": "ControlOverviewPageCreateTaskMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "CreateTaskPayload",
"kind": "LinkedField",
"name": "createTask",
"plural": false,
"selections": [
(v3/*: any*/)
],
"storageKey": null
}
],
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [
(v1/*: any*/),
(v0/*: any*/)
],
"kind": "Operation",
"name": "ControlOverviewPageCreateTaskMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "CreateTaskPayload",
"kind": "LinkedField",
"name": "createTask",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"filters": null,
"handle": "prependEdge",
"key": "",
"kind": "LinkedHandle",
"name": "taskEdge",
"handleArgs": [
{
"kind": "Variable",
"name": "connections",
"variableName": "connections"
}
]
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "a181d85ad48dde4a694a7def2700ee96",
"id": null,
"metadata": {},
"name": "ControlOverviewPageCreateTaskMutation",
"operationKind": "mutation",
"text": "mutation ControlOverviewPageCreateTaskMutation(\n $input: CreateTaskInput!\n) {\n createTask(input: $input) {\n taskEdge {\n node {\n id\n name\n description\n state\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "3e4ccc4d984d30492fc65afc16e6930c";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<3e556c8d6b7896963c5433de8acca792>>
* @generated SignedSource<<91fa70daabd409bc43d5e25a2fa98a13>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -22,6 +22,7 @@ export type ControlOverviewPageQuery$data = {
readonly name?: string;
readonly state?: ControlState;
readonly tasks?: {
readonly __id: string;
readonly edges: ReadonlyArray<{
readonly node: {
readonly description: string;
@@ -82,59 +83,99 @@ v5 = {
"storageKey": null
},
v6 = {
"kind": "InlineFragment",
"selections": [
(v3/*: any*/),
(v4/*: any*/),
(v5/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "category",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "TaskConnection",
"kind": "LinkedField",
"name": "tasks",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TaskEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Task",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
(v3/*: any*/),
(v4/*: any*/),
(v5/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"type": "Control",
"abstractKey": null
};
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "category",
"storageKey": null
},
v7 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
v8 = [
{
"alias": null,
"args": null,
"concreteType": "TaskEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Task",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
(v3/*: any*/),
(v4/*: any*/),
(v5/*: any*/),
(v7/*: any*/)
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
}
],
"storageKey": null
},
{
"kind": "ClientExtension",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__id",
"storageKey": null
}
]
}
],
v9 = [
{
"kind": "Literal",
"name": "first",
"value": 100
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
@@ -151,7 +192,27 @@ return {
"plural": false,
"selections": [
(v2/*: any*/),
(v6/*: any*/)
{
"kind": "InlineFragment",
"selections": [
(v3/*: any*/),
(v4/*: any*/),
(v5/*: any*/),
(v6/*: any*/),
{
"alias": "tasks",
"args": null,
"concreteType": "TaskConnection",
"kind": "LinkedField",
"name": "__ControlOverviewPage_tasks_connection",
"plural": false,
"selections": (v8/*: any*/),
"storageKey": null
}
],
"type": "Control",
"abstractKey": null
}
],
"storageKey": null
}
@@ -173,31 +234,66 @@ return {
"name": "node",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
(v7/*: any*/),
(v2/*: any*/),
(v6/*: any*/)
{
"kind": "InlineFragment",
"selections": [
(v3/*: any*/),
(v4/*: any*/),
(v5/*: any*/),
(v6/*: any*/),
{
"alias": null,
"args": (v9/*: any*/),
"concreteType": "TaskConnection",
"kind": "LinkedField",
"name": "tasks",
"plural": false,
"selections": (v8/*: any*/),
"storageKey": "tasks(first:100)"
},
{
"alias": null,
"args": (v9/*: any*/),
"filters": null,
"handle": "connection",
"key": "ControlOverviewPage_tasks",
"kind": "LinkedHandle",
"name": "tasks"
}
],
"type": "Control",
"abstractKey": null
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "130c195db0e9055a63b88ea5eee8b3aa",
"cacheID": "a13eb28fcbe162deaff92653c19cc22a",
"id": null,
"metadata": {},
"metadata": {
"connection": [
{
"count": null,
"cursor": null,
"direction": "forward",
"path": [
"control",
"tasks"
]
}
]
},
"name": "ControlOverviewPageQuery",
"operationKind": "query",
"text": "query ControlOverviewPageQuery(\n $controlId: ID!\n) {\n control: node(id: $controlId) {\n __typename\n id\n ... on Control {\n name\n description\n state\n category\n tasks {\n edges {\n node {\n id\n name\n description\n state\n }\n }\n }\n }\n }\n}\n"
"text": "query ControlOverviewPageQuery(\n $controlId: ID!\n) {\n control: node(id: $controlId) {\n __typename\n id\n ... on Control {\n name\n description\n state\n category\n tasks(first: 100) {\n edges {\n node {\n id\n name\n description\n state\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "1feb1b6d25907ac45205183c39092d35";
(node as any).hash = "dfdddd9d98741d1e2bfabf4d4430756f";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<191b5c89c10a7d949997216a79f49598>>
* @generated SignedSource<<168d543d518d84054b1f194ee13feeaa>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -19,11 +19,9 @@ export type ControlOverviewPageUpdateTaskStateMutation$variables = {
};
export type ControlOverviewPageUpdateTaskStateMutation$data = {
readonly updateTaskState: {
readonly taskEdge: {
readonly node: {
readonly id: string;
readonly state: TaskState;
};
readonly task: {
readonly id: string;
readonly state: TaskState;
};
};
};
@@ -58,34 +56,23 @@ v1 = [
{
"alias": null,
"args": null,
"concreteType": "TaskEdge",
"concreteType": "Task",
"kind": "LinkedField",
"name": "taskEdge",
"name": "task",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Task",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "state",
"storageKey": null
}
],
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "state",
"storageKey": null
}
],
@@ -113,16 +100,16 @@ return {
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "ea56b9c3f41a4b69a3643884f8f2306d",
"cacheID": "7099089b96d6ca450d88f0a3597b2203",
"id": null,
"metadata": {},
"name": "ControlOverviewPageUpdateTaskStateMutation",
"operationKind": "mutation",
"text": "mutation ControlOverviewPageUpdateTaskStateMutation(\n $input: UpdateTaskStateInput!\n) {\n updateTaskState(input: $input) {\n taskEdge {\n node {\n id\n state\n }\n }\n }\n}\n"
"text": "mutation ControlOverviewPageUpdateTaskStateMutation(\n $input: UpdateTaskStateInput!\n) {\n updateTaskState(input: $input) {\n task {\n id\n state\n }\n }\n}\n"
}
};
})();
(node as any).hash = "3113753b83cf06bd8aca41610c1e89fd";
(node as any).hash = "a81e1f58fa9931a70b85633b6ad0bd7a";
export default node;

View File

@@ -392,6 +392,7 @@ type Mutation {
input: DeleteOrganizationInput!
): DeleteOrganizationPayload!
updateTaskState(input: UpdateTaskStateInput!): UpdateTaskStatePayload!
createTask(input: CreateTaskInput!): CreateTaskPayload!
}
input CreateVendorInput {
@@ -518,5 +519,15 @@ input UpdateTaskStateInput {
}
type UpdateTaskStatePayload {
task: Task!
}
input CreateTaskInput {
controlId: ID!
name: String!
description: String!
}
type CreateTaskPayload {
taskEdge: TaskEdge!
}

View File

@@ -105,6 +105,10 @@ type ComplexityRoot struct {
PeopleEdge func(childComplexity int) int
}
CreateTaskPayload struct {
TaskEdge func(childComplexity int) int
}
CreateVendorPayload struct {
VendorEdge func(childComplexity int) int
}
@@ -183,6 +187,7 @@ type ComplexityRoot struct {
Mutation struct {
CreateOrganization func(childComplexity int, input types.CreateOrganizationInput) int
CreatePeople func(childComplexity int, input types.CreatePeopleInput) int
CreateTask func(childComplexity int, input types.CreateTaskInput) int
CreateVendor func(childComplexity int, input types.CreateVendorInput) int
DeleteOrganization func(childComplexity int, input types.DeleteOrganizationInput) int
DeletePeople func(childComplexity int, input types.DeletePeopleInput) int
@@ -292,7 +297,7 @@ type ComplexityRoot struct {
}
UpdateTaskStatePayload struct {
TaskEdge func(childComplexity int) int
Task func(childComplexity int) int
}
User struct {
@@ -351,6 +356,7 @@ type MutationResolver interface {
CreateOrganization(ctx context.Context, input types.CreateOrganizationInput) (*types.CreateOrganizationPayload, error)
DeleteOrganization(ctx context.Context, input types.DeleteOrganizationInput) (*types.DeleteOrganizationPayload, error)
UpdateTaskState(ctx context.Context, input types.UpdateTaskStateInput) (*types.UpdateTaskStatePayload, error)
CreateTask(ctx context.Context, input types.CreateTaskInput) (*types.CreateTaskPayload, error)
}
type OrganizationResolver interface {
Frameworks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.FrameworkConnection, error)
@@ -573,6 +579,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.CreatePeoplePayload.PeopleEdge(childComplexity), true
case "CreateTaskPayload.taskEdge":
if e.complexity.CreateTaskPayload.TaskEdge == nil {
break
}
return e.complexity.CreateTaskPayload.TaskEdge(childComplexity), true
case "CreateVendorPayload.vendorEdge":
if e.complexity.CreateVendorPayload.VendorEdge == nil {
break
@@ -859,6 +872,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.CreatePeople(childComplexity, args["input"].(types.CreatePeopleInput)), true
case "Mutation.createTask":
if e.complexity.Mutation.CreateTask == nil {
break
}
args, err := ec.field_Mutation_createTask_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.CreateTask(childComplexity, args["input"].(types.CreateTaskInput)), true
case "Mutation.createVendor":
if e.complexity.Mutation.CreateVendor == nil {
break
@@ -1351,12 +1376,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.TaskStateTransitionEdge.Node(childComplexity), true
case "UpdateTaskStatePayload.taskEdge":
if e.complexity.UpdateTaskStatePayload.TaskEdge == nil {
case "UpdateTaskStatePayload.task":
if e.complexity.UpdateTaskStatePayload.Task == nil {
break
}
return e.complexity.UpdateTaskStatePayload.TaskEdge(childComplexity), true
return e.complexity.UpdateTaskStatePayload.Task(childComplexity), true
case "User.createdAt":
if e.complexity.User.CreatedAt == nil {
@@ -1534,6 +1559,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
inputUnmarshalMap := graphql.BuildUnmarshalerMap(
ec.unmarshalInputCreateOrganizationInput,
ec.unmarshalInputCreatePeopleInput,
ec.unmarshalInputCreateTaskInput,
ec.unmarshalInputCreateVendorInput,
ec.unmarshalInputDeleteOrganizationInput,
ec.unmarshalInputDeletePeopleInput,
@@ -2032,6 +2058,7 @@ type Mutation {
input: DeleteOrganizationInput!
): DeleteOrganizationPayload!
updateTaskState(input: UpdateTaskStateInput!): UpdateTaskStatePayload!
createTask(input: CreateTaskInput!): CreateTaskPayload!
}
input CreateVendorInput {
@@ -2158,6 +2185,16 @@ input UpdateTaskStateInput {
}
type UpdateTaskStatePayload {
task: Task!
}
input CreateTaskInput {
controlId: ID!
name: String!
description: String!
}
type CreateTaskPayload {
taskEdge: TaskEdge!
}
`, BuiltIn: false},
@@ -2522,6 +2559,29 @@ func (ec *executionContext) field_Mutation_createPeople_argsInput(
return zeroVal, nil
}
func (ec *executionContext) field_Mutation_createTask_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error
args := map[string]any{}
arg0, err := ec.field_Mutation_createTask_argsInput(ctx, rawArgs)
if err != nil {
return nil, err
}
args["input"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation_createTask_argsInput(
ctx context.Context,
rawArgs map[string]any,
) (types.CreateTaskInput, error) {
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("input"))
if tmp, ok := rawArgs["input"]; ok {
return ec.unmarshalNCreateTaskInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐCreateTaskInput(ctx, tmp)
}
var zeroVal types.CreateTaskInput
return zeroVal, nil
}
func (ec *executionContext) field_Mutation_createVendor_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error
args := map[string]any{}
@@ -4289,6 +4349,50 @@ func (ec *executionContext) fieldContext_CreatePeoplePayload_peopleEdge(_ contex
return fc, nil
}
func (ec *executionContext) _CreateTaskPayload_taskEdge(ctx context.Context, field graphql.CollectedField, obj *types.CreateTaskPayload) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_CreateTaskPayload_taskEdge(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 obj.TaskEdge, nil
})
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.TaskEdge)
fc.Result = res
return ec.marshalNTaskEdge2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐTaskEdge(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_CreateTaskPayload_taskEdge(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "CreateTaskPayload",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "cursor":
return ec.fieldContext_TaskEdge_cursor(ctx, field)
case "node":
return ec.fieldContext_TaskEdge_node(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type TaskEdge", field.Name)
},
}
return fc, nil
}
func (ec *executionContext) _CreateVendorPayload_vendorEdge(ctx context.Context, field graphql.CollectedField, obj *types.CreateVendorPayload) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_CreateVendorPayload_vendorEdge(ctx, field)
if err != nil {
@@ -6220,8 +6324,8 @@ func (ec *executionContext) fieldContext_Mutation_updateTaskState(ctx context.Co
IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "taskEdge":
return ec.fieldContext_UpdateTaskStatePayload_taskEdge(ctx, field)
case "task":
return ec.fieldContext_UpdateTaskStatePayload_task(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type UpdateTaskStatePayload", field.Name)
},
@@ -6234,6 +6338,53 @@ func (ec *executionContext) fieldContext_Mutation_updateTaskState(ctx context.Co
return fc, nil
}
func (ec *executionContext) _Mutation_createTask(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Mutation_createTask(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.Mutation().CreateTask(rctx, fc.Args["input"].(types.CreateTaskInput))
})
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.CreateTaskPayload)
fc.Result = res
return ec.marshalNCreateTaskPayload2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐCreateTaskPayload(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_Mutation_createTask(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Mutation",
Field: field,
IsMethod: true,
IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "taskEdge":
return ec.fieldContext_CreateTaskPayload_taskEdge(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type CreateTaskPayload", field.Name)
},
}
ctx = graphql.WithFieldContext(ctx, fc)
if fc.Args, err = ec.field_Mutation_createTask_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
ec.Error(ctx, err)
return fc, err
}
return fc, nil
}
func (ec *executionContext) _Organization_id(ctx context.Context, field graphql.CollectedField, obj *types.Organization) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Organization_id(ctx, field)
if err != nil {
@@ -8591,15 +8742,15 @@ func (ec *executionContext) fieldContext_TaskStateTransitionEdge_node(_ context.
return fc, nil
}
func (ec *executionContext) _UpdateTaskStatePayload_taskEdge(ctx context.Context, field graphql.CollectedField, obj *types.UpdateTaskStatePayload) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_UpdateTaskStatePayload_taskEdge(ctx, field)
func (ec *executionContext) _UpdateTaskStatePayload_task(ctx context.Context, field graphql.CollectedField, obj *types.UpdateTaskStatePayload) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_UpdateTaskStatePayload_task(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 obj.TaskEdge, nil
return obj.Task, nil
})
if err != nil {
ec.Error(ctx, err)
@@ -8611,12 +8762,12 @@ func (ec *executionContext) _UpdateTaskStatePayload_taskEdge(ctx context.Context
}
return graphql.Null
}
res := resTmp.(*types.TaskEdge)
res := resTmp.(*types.Task)
fc.Result = res
return ec.marshalNTaskEdge2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐTaskEdge(ctx, field.Selections, res)
return ec.marshalNTask2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐTask(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_UpdateTaskStatePayload_taskEdge(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
func (ec *executionContext) fieldContext_UpdateTaskStatePayload_task(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "UpdateTaskStatePayload",
Field: field,
@@ -8624,12 +8775,24 @@ func (ec *executionContext) fieldContext_UpdateTaskStatePayload_taskEdge(_ conte
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "cursor":
return ec.fieldContext_TaskEdge_cursor(ctx, field)
case "node":
return ec.fieldContext_TaskEdge_node(ctx, field)
case "id":
return ec.fieldContext_Task_id(ctx, field)
case "name":
return ec.fieldContext_Task_name(ctx, field)
case "description":
return ec.fieldContext_Task_description(ctx, field)
case "state":
return ec.fieldContext_Task_state(ctx, field)
case "stateTransisions":
return ec.fieldContext_Task_stateTransisions(ctx, field)
case "evidences":
return ec.fieldContext_Task_evidences(ctx, field)
case "createdAt":
return ec.fieldContext_Task_createdAt(ctx, field)
case "updatedAt":
return ec.fieldContext_Task_updatedAt(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type TaskEdge", field.Name)
return nil, fmt.Errorf("no field named %q was found under type Task", field.Name)
},
}
return fc, nil
@@ -11185,6 +11348,47 @@ func (ec *executionContext) unmarshalInputCreatePeopleInput(ctx context.Context,
return it, nil
}
func (ec *executionContext) unmarshalInputCreateTaskInput(ctx context.Context, obj any) (types.CreateTaskInput, error) {
var it types.CreateTaskInput
asMap := map[string]any{}
for k, v := range obj.(map[string]any) {
asMap[k] = v
}
fieldsInOrder := [...]string{"controlId", "name", "description"}
for _, k := range fieldsInOrder {
v, ok := asMap[k]
if !ok {
continue
}
switch k {
case "controlId":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("controlId"))
data, err := ec.unmarshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGID(ctx, v)
if err != nil {
return it, err
}
it.ControlID = data
case "name":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("name"))
data, err := ec.unmarshalNString2string(ctx, v)
if err != nil {
return it, err
}
it.Name = data
case "description":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("description"))
data, err := ec.unmarshalNString2string(ctx, v)
if err != nil {
return it, err
}
it.Description = data
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputCreateVendorInput(ctx context.Context, obj any) (types.CreateVendorInput, error) {
var it types.CreateVendorInput
asMap := map[string]any{}
@@ -12065,6 +12269,45 @@ func (ec *executionContext) _CreatePeoplePayload(ctx context.Context, sel ast.Se
return out
}
var createTaskPayloadImplementors = []string{"CreateTaskPayload"}
func (ec *executionContext) _CreateTaskPayload(ctx context.Context, sel ast.SelectionSet, obj *types.CreateTaskPayload) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, createTaskPayloadImplementors)
out := graphql.NewFieldSet(fields)
deferred := make(map[string]*graphql.FieldSet)
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("CreateTaskPayload")
case "taskEdge":
out.Values[i] = ec._CreateTaskPayload_taskEdge(ctx, field, obj)
if out.Values[i] == graphql.Null {
out.Invalids++
}
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch(ctx)
if out.Invalids > 0 {
return graphql.Null
}
atomic.AddInt32(&ec.deferred, int32(len(deferred)))
for label, dfs := range deferred {
ec.processDeferredGroup(graphql.DeferredGroup{
Label: label,
Path: graphql.GetPath(ctx),
FieldSet: dfs,
Context: ctx,
})
}
return out
}
var createVendorPayloadImplementors = []string{"CreateVendorPayload"}
func (ec *executionContext) _CreateVendorPayload(ctx context.Context, sel ast.SelectionSet, obj *types.CreateVendorPayload) graphql.Marshaler {
@@ -12815,6 +13058,13 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
if out.Values[i] == graphql.Null {
out.Invalids++
}
case "createTask":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_createTask(ctx, field)
})
if out.Values[i] == graphql.Null {
out.Invalids++
}
default:
panic("unknown field " + strconv.Quote(field.Name))
}
@@ -13787,8 +14037,8 @@ func (ec *executionContext) _UpdateTaskStatePayload(ctx context.Context, sel ast
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("UpdateTaskStatePayload")
case "taskEdge":
out.Values[i] = ec._UpdateTaskStatePayload_taskEdge(ctx, field, obj)
case "task":
out.Values[i] = ec._UpdateTaskStatePayload_task(ctx, field, obj)
if out.Values[i] == graphql.Null {
out.Invalids++
}
@@ -14634,6 +14884,25 @@ func (ec *executionContext) marshalNCreatePeoplePayload2ᚖgithubᚗcomᚋgetpro
return ec._CreatePeoplePayload(ctx, sel, v)
}
func (ec *executionContext) unmarshalNCreateTaskInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐCreateTaskInput(ctx context.Context, v any) (types.CreateTaskInput, error) {
res, err := ec.unmarshalInputCreateTaskInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) marshalNCreateTaskPayload2githubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐCreateTaskPayload(ctx context.Context, sel ast.SelectionSet, v types.CreateTaskPayload) graphql.Marshaler {
return ec._CreateTaskPayload(ctx, sel, &v)
}
func (ec *executionContext) marshalNCreateTaskPayload2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐCreateTaskPayload(ctx context.Context, sel ast.SelectionSet, v *types.CreateTaskPayload) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "the requested element is null which the schema does not allow")
}
return graphql.Null
}
return ec._CreateTaskPayload(ctx, sel, v)
}
func (ec *executionContext) unmarshalNCreateVendorInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐCreateVendorInput(ctx context.Context, v any) (types.CreateVendorInput, error) {
res, err := ec.unmarshalInputCreateVendorInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)

View File

@@ -79,6 +79,16 @@ type CreatePeoplePayload struct {
PeopleEdge *PeopleEdge `json:"peopleEdge"`
}
type CreateTaskInput struct {
ControlID gid.GID `json:"controlId"`
Name string `json:"name"`
Description string `json:"description"`
}
type CreateTaskPayload struct {
TaskEdge *TaskEdge `json:"taskEdge"`
}
type CreateVendorInput struct {
OrganizationID gid.GID `json:"organizationId"`
Name string `json:"name"`
@@ -309,7 +319,7 @@ type UpdateTaskStateInput struct {
}
type UpdateTaskStatePayload struct {
TaskEdge *TaskEdge `json:"taskEdge"`
Task *Task `json:"task"`
}
type UpdateVendorInput struct {

View File

@@ -205,6 +205,22 @@ func (r *mutationResolver) UpdateTaskState(ctx context.Context, input types.Upda
}
return &types.UpdateTaskStatePayload{
Task: types.NewTask(task),
}, nil
}
// CreateTask is the resolver for the createTask field.
func (r *mutationResolver) CreateTask(ctx context.Context, input types.CreateTaskInput) (*types.CreateTaskPayload, error) {
task, err := r.proboSvc.CreateTask(ctx, probo.CreateTaskRequest{
ControlID: input.ControlID,
Name: input.Name,
Description: input.Description,
})
if err != nil {
return nil, fmt.Errorf("cannot create task: %w", err)
}
return &types.CreateTaskPayload{
TaskEdge: types.NewTaskEdge(task),
}, nil
}

View File

@@ -171,13 +171,13 @@ VALUES (
`
args := pgx.NamedArgs{
"control_id": t.ID,
"framework_id": t.ControlID,
"name": t.Name,
"description": t.Description,
"content_ref": t.ContentRef,
"created_at": t.CreatedAt,
"updated_at": t.UpdatedAt,
"task_id": t.ID,
"control_id": t.ControlID,
"name": t.Name,
"description": t.Description,
"content_ref": t.ContentRef,
"created_at": t.CreatedAt,
"updated_at": t.UpdatedAt,
}
_, err := conn.Exec(ctx, q, args)
return err

View File

@@ -27,9 +27,10 @@ import (
type (
CreateTaskRequest struct {
ControlID gid.GID
Name string
ContentRef string
ControlID gid.GID
Name string
ContentRef string
Description string
}
)
@@ -49,13 +50,14 @@ func (s Service) CreateTask(
control := &coredata.Control{}
task := &coredata.Task{
ID: taskID,
ControlID: req.ControlID,
Name: req.Name,
ContentRef: req.ContentRef,
State: coredata.TaskStateTodo,
CreatedAt: now,
UpdatedAt: now,
ID: taskID,
ControlID: req.ControlID,
Name: req.Name,
ContentRef: req.ContentRef,
Description: req.Description,
State: coredata.TaskStateTodo,
CreatedAt: now,
UpdatedAt: now,
}
taskStateTransition := coredata.TaskStateTransition{