Add delete task

Signed-off-by: gearnode <bryan@frimin.fr>
This commit is contained in:
gearnode
2025-02-26 15:58:51 +01:00
parent eaaab56968
commit 6be112e340
8 changed files with 580 additions and 11 deletions

View File

@@ -7,7 +7,7 @@ import {
useQueryLoader,
useMutation,
} from "react-relay";
import { CheckCircle2, Plus } from "lucide-react";
import { CheckCircle2, Plus, Trash2 } from "lucide-react";
import { Card, CardContent } from "@/components/ui/card";
import { useToast } from "@/hooks/use-toast";
import { Button } from "@/components/ui/button";
@@ -26,6 +26,8 @@ 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";
// The import below will be generated after the first run
// import type { ControlOverviewPageDeleteTaskMutation as ControlOverviewPageDeleteTaskMutationType } from "./__generated__/ControlOverviewPageDeleteTaskMutation.graphql";
const controlOverviewPageQuery = graphql`
query ControlOverviewPageQuery($controlId: ID!) {
@@ -83,6 +85,17 @@ const createTaskMutation = graphql`
}
`;
const deleteTaskMutation = graphql`
mutation ControlOverviewPageDeleteTaskMutation(
$input: DeleteTaskInput!
$connections: [ID!]!
) {
deleteTask(input: $input) {
deletedTaskId @deleteEdge(connections: $connections)
}
}
`;
function ControlOverviewPageContent({
queryRef,
}: {
@@ -99,6 +112,8 @@ function ControlOverviewPageContent({
);
const [createTask] =
useMutation<ControlOverviewPageCreateTaskMutationType>(createTaskMutation);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const [deleteTask] = useMutation<any>(deleteTaskMutation);
const control = data.control;
const tasks = control?.tasks?.edges.map((edge) => edge?.node) ?? [];
@@ -107,6 +122,13 @@ function ControlOverviewPageContent({
const [newTaskName, setNewTaskName] = useState("");
const [newTaskDescription, setNewTaskDescription] = useState("");
// State for the delete task dialog
const [isDeleteTaskOpen, setIsDeleteTaskOpen] = useState(false);
const [taskToDelete, setTaskToDelete] = useState<{
id: string;
name: string;
} | null>(null);
const handleTaskClick = (taskId: string, currentState: string) => {
const newState = currentState === "DONE" ? "TODO" : "DONE";
@@ -192,6 +214,39 @@ function ControlOverviewPageContent({
});
};
const handleDeleteTask = (taskId: string, taskName: string) => {
setTaskToDelete({ id: taskId, name: taskName });
setIsDeleteTaskOpen(true);
};
const confirmDeleteTask = () => {
if (!taskToDelete) return;
deleteTask({
variables: {
connections: [`${data.control?.tasks?.__id}`],
input: {
taskId: taskToDelete.id,
},
},
onCompleted: () => {
toast({
title: "Task deleted",
description: "Task has been deleted successfully.",
});
setIsDeleteTaskOpen(false);
setTaskToDelete(null);
},
onError: (error) => {
toast({
title: "Error deleting task",
description: error.message,
variant: "destructive",
});
},
});
};
return (
<div className="min-h-screen bg-white p-6 space-y-6">
<div className="space-y-4">
@@ -339,16 +394,16 @@ function ControlOverviewPageContent({
</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>
<button className="text-gray-400 hover:text-gray-600">
<svg className="w-6 h-6" fill="none" viewBox="0 0 24 24">
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M12 12h.01M12 6h.01M12 18h.01"
/>
</svg>
<button
className="text-gray-400 hover:text-red-600"
onClick={(e) => {
e.stopPropagation();
if (task?.id && task?.name) {
handleDeleteTask(task.id, task.name);
}
}}
>
<Trash2 className="w-4 h-4" />
</button>
</div>
</div>
@@ -362,6 +417,30 @@ function ControlOverviewPageContent({
)}
</div>
</div>
{/* Delete Task Confirmation Dialog */}
<Dialog open={isDeleteTaskOpen} onOpenChange={setIsDeleteTaskOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>Delete Task</DialogTitle>
<DialogDescription>
Are you sure you want to delete the task &quot;
{taskToDelete?.name}&quot;? This action cannot be undone.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button
variant="outline"
onClick={() => setIsDeleteTaskOpen(false)}
>
Cancel
</Button>
<Button variant="destructive" onClick={confirmDeleteTask}>
Delete
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
}

View File

@@ -0,0 +1,132 @@
/**
* @generated SignedSource<<cd5744c9739535825e9922b9cb95f034>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type DeleteTaskInput = {
taskId: string;
};
export type ControlOverviewPageDeleteTaskMutation$variables = {
connections: ReadonlyArray<string>;
input: DeleteTaskInput;
};
export type ControlOverviewPageDeleteTaskMutation$data = {
readonly deleteTask: {
readonly deletedTaskId: string;
};
};
export type ControlOverviewPageDeleteTaskMutation = {
response: ControlOverviewPageDeleteTaskMutation$data;
variables: ControlOverviewPageDeleteTaskMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "connections"
},
v1 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
},
v2 = [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "deletedTaskId",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": [
(v0/*: any*/),
(v1/*: any*/)
],
"kind": "Fragment",
"metadata": null,
"name": "ControlOverviewPageDeleteTaskMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "DeleteTaskPayload",
"kind": "LinkedField",
"name": "deleteTask",
"plural": false,
"selections": [
(v3/*: any*/)
],
"storageKey": null
}
],
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [
(v1/*: any*/),
(v0/*: any*/)
],
"kind": "Operation",
"name": "ControlOverviewPageDeleteTaskMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "DeleteTaskPayload",
"kind": "LinkedField",
"name": "deleteTask",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"filters": null,
"handle": "deleteEdge",
"key": "",
"kind": "ScalarHandle",
"name": "deletedTaskId",
"handleArgs": [
{
"kind": "Variable",
"name": "connections",
"variableName": "connections"
}
]
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "06db9599a3e83354c0e865d08cdb29dd",
"id": null,
"metadata": {},
"name": "ControlOverviewPageDeleteTaskMutation",
"operationKind": "mutation",
"text": "mutation ControlOverviewPageDeleteTaskMutation(\n $input: DeleteTaskInput!\n) {\n deleteTask(input: $input) {\n deletedTaskId\n }\n}\n"
}
};
})();
(node as any).hash = "885a98007e48ab3175d0480c5a750e19";
export default node;

View File

@@ -393,6 +393,7 @@ type Mutation {
): DeleteOrganizationPayload!
updateTaskState(input: UpdateTaskStateInput!): UpdateTaskStatePayload!
createTask(input: CreateTaskInput!): CreateTaskPayload!
deleteTask(input: DeleteTaskInput!): DeleteTaskPayload!
}
input CreateVendorInput {
@@ -531,3 +532,11 @@ input CreateTaskInput {
type CreateTaskPayload {
taskEdge: TaskEdge!
}
input DeleteTaskInput {
taskId: ID!
}
type DeleteTaskPayload {
deletedTaskId: ID!
}

View File

@@ -121,6 +121,10 @@ type ComplexityRoot struct {
DeletedPeopleID func(childComplexity int) int
}
DeleteTaskPayload struct {
DeletedTaskID func(childComplexity int) int
}
DeleteVendorPayload struct {
DeletedVendorID func(childComplexity int) int
}
@@ -191,6 +195,7 @@ type ComplexityRoot struct {
CreateVendor func(childComplexity int, input types.CreateVendorInput) int
DeleteOrganization func(childComplexity int, input types.DeleteOrganizationInput) int
DeletePeople func(childComplexity int, input types.DeletePeopleInput) int
DeleteTask func(childComplexity int, input types.DeleteTaskInput) int
DeleteVendor func(childComplexity int, input types.DeleteVendorInput) int
UpdatePeople func(childComplexity int, input types.UpdatePeopleInput) int
UpdateTaskState func(childComplexity int, input types.UpdateTaskStateInput) int
@@ -357,6 +362,7 @@ type MutationResolver interface {
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)
DeleteTask(ctx context.Context, input types.DeleteTaskInput) (*types.DeleteTaskPayload, error)
}
type OrganizationResolver interface {
Frameworks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.FrameworkConnection, error)
@@ -607,6 +613,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.DeletePeoplePayload.DeletedPeopleID(childComplexity), true
case "DeleteTaskPayload.deletedTaskId":
if e.complexity.DeleteTaskPayload.DeletedTaskID == nil {
break
}
return e.complexity.DeleteTaskPayload.DeletedTaskID(childComplexity), true
case "DeleteVendorPayload.deletedVendorId":
if e.complexity.DeleteVendorPayload.DeletedVendorID == nil {
break
@@ -920,6 +933,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.DeletePeople(childComplexity, args["input"].(types.DeletePeopleInput)), true
case "Mutation.deleteTask":
if e.complexity.Mutation.DeleteTask == nil {
break
}
args, err := ec.field_Mutation_deleteTask_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.DeleteTask(childComplexity, args["input"].(types.DeleteTaskInput)), true
case "Mutation.deleteVendor":
if e.complexity.Mutation.DeleteVendor == nil {
break
@@ -1563,6 +1588,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
ec.unmarshalInputCreateVendorInput,
ec.unmarshalInputDeleteOrganizationInput,
ec.unmarshalInputDeletePeopleInput,
ec.unmarshalInputDeleteTaskInput,
ec.unmarshalInputDeleteVendorInput,
ec.unmarshalInputUpdatePeopleInput,
ec.unmarshalInputUpdateTaskStateInput,
@@ -2059,6 +2085,7 @@ type Mutation {
): DeleteOrganizationPayload!
updateTaskState(input: UpdateTaskStateInput!): UpdateTaskStatePayload!
createTask(input: CreateTaskInput!): CreateTaskPayload!
deleteTask(input: DeleteTaskInput!): DeleteTaskPayload!
}
input CreateVendorInput {
@@ -2197,6 +2224,14 @@ input CreateTaskInput {
type CreateTaskPayload {
taskEdge: TaskEdge!
}
input DeleteTaskInput {
taskId: ID!
}
type DeleteTaskPayload {
deletedTaskId: ID!
}
`, BuiltIn: false},
}
var parsedSchema = gqlparser.MustLoadSchema(sources...)
@@ -2651,6 +2686,29 @@ func (ec *executionContext) field_Mutation_deletePeople_argsInput(
return zeroVal, nil
}
func (ec *executionContext) field_Mutation_deleteTask_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error
args := map[string]any{}
arg0, err := ec.field_Mutation_deleteTask_argsInput(ctx, rawArgs)
if err != nil {
return nil, err
}
args["input"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation_deleteTask_argsInput(
ctx context.Context,
rawArgs map[string]any,
) (types.DeleteTaskInput, error) {
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("input"))
if tmp, ok := rawArgs["input"]; ok {
return ec.unmarshalNDeleteTaskInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐDeleteTaskInput(ctx, tmp)
}
var zeroVal types.DeleteTaskInput
return zeroVal, nil
}
func (ec *executionContext) field_Mutation_deleteVendor_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error
args := map[string]any{}
@@ -4513,6 +4571,44 @@ func (ec *executionContext) fieldContext_DeletePeoplePayload_deletedPeopleId(_ c
return fc, nil
}
func (ec *executionContext) _DeleteTaskPayload_deletedTaskId(ctx context.Context, field graphql.CollectedField, obj *types.DeleteTaskPayload) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_DeleteTaskPayload_deletedTaskId(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.DeletedTaskID, 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.(gid.GID)
fc.Result = res
return ec.marshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGID(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_DeleteTaskPayload_deletedTaskId(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "DeleteTaskPayload",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type ID does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _DeleteVendorPayload_deletedVendorId(ctx context.Context, field graphql.CollectedField, obj *types.DeleteVendorPayload) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_DeleteVendorPayload_deletedVendorId(ctx, field)
if err != nil {
@@ -6385,6 +6481,53 @@ func (ec *executionContext) fieldContext_Mutation_createTask(ctx context.Context
return fc, nil
}
func (ec *executionContext) _Mutation_deleteTask(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Mutation_deleteTask(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().DeleteTask(rctx, fc.Args["input"].(types.DeleteTaskInput))
})
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.DeleteTaskPayload)
fc.Result = res
return ec.marshalNDeleteTaskPayload2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐDeleteTaskPayload(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_Mutation_deleteTask(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 "deletedTaskId":
return ec.fieldContext_DeleteTaskPayload_deletedTaskId(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type DeleteTaskPayload", field.Name)
},
}
ctx = graphql.WithFieldContext(ctx, fc)
if fc.Args, err = ec.field_Mutation_deleteTask_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 {
@@ -11533,6 +11676,33 @@ func (ec *executionContext) unmarshalInputDeletePeopleInput(ctx context.Context,
return it, nil
}
func (ec *executionContext) unmarshalInputDeleteTaskInput(ctx context.Context, obj any) (types.DeleteTaskInput, error) {
var it types.DeleteTaskInput
asMap := map[string]any{}
for k, v := range obj.(map[string]any) {
asMap[k] = v
}
fieldsInOrder := [...]string{"taskId"}
for _, k := range fieldsInOrder {
v, ok := asMap[k]
if !ok {
continue
}
switch k {
case "taskId":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("taskId"))
data, err := ec.unmarshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGID(ctx, v)
if err != nil {
return it, err
}
it.TaskID = data
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputDeleteVendorInput(ctx context.Context, obj any) (types.DeleteVendorInput, error) {
var it types.DeleteVendorInput
asMap := map[string]any{}
@@ -12425,6 +12595,45 @@ func (ec *executionContext) _DeletePeoplePayload(ctx context.Context, sel ast.Se
return out
}
var deleteTaskPayloadImplementors = []string{"DeleteTaskPayload"}
func (ec *executionContext) _DeleteTaskPayload(ctx context.Context, sel ast.SelectionSet, obj *types.DeleteTaskPayload) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, deleteTaskPayloadImplementors)
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("DeleteTaskPayload")
case "deletedTaskId":
out.Values[i] = ec._DeleteTaskPayload_deletedTaskId(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 deleteVendorPayloadImplementors = []string{"DeleteVendorPayload"}
func (ec *executionContext) _DeleteVendorPayload(ctx context.Context, sel ast.SelectionSet, obj *types.DeleteVendorPayload) graphql.Marshaler {
@@ -13065,6 +13274,13 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
if out.Values[i] == graphql.Null {
out.Invalids++
}
case "deleteTask":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_deleteTask(ctx, field)
})
if out.Values[i] == graphql.Null {
out.Invalids++
}
default:
panic("unknown field " + strconv.Quote(field.Name))
}
@@ -14990,6 +15206,25 @@ func (ec *executionContext) marshalNDeletePeoplePayload2ᚖgithubᚗcomᚋgetpro
return ec._DeletePeoplePayload(ctx, sel, v)
}
func (ec *executionContext) unmarshalNDeleteTaskInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐDeleteTaskInput(ctx context.Context, v any) (types.DeleteTaskInput, error) {
res, err := ec.unmarshalInputDeleteTaskInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) marshalNDeleteTaskPayload2githubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐDeleteTaskPayload(ctx context.Context, sel ast.SelectionSet, v types.DeleteTaskPayload) graphql.Marshaler {
return ec._DeleteTaskPayload(ctx, sel, &v)
}
func (ec *executionContext) marshalNDeleteTaskPayload2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐDeleteTaskPayload(ctx context.Context, sel ast.SelectionSet, v *types.DeleteTaskPayload) 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._DeleteTaskPayload(ctx, sel, v)
}
func (ec *executionContext) unmarshalNDeleteVendorInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐDeleteVendorInput(ctx context.Context, v any) (types.DeleteVendorInput, error) {
res, err := ec.unmarshalInputDeleteVendorInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)

View File

@@ -122,6 +122,14 @@ type DeletePeoplePayload struct {
DeletedPeopleID gid.GID `json:"deletedPeopleId"`
}
type DeleteTaskInput struct {
TaskID gid.GID `json:"taskId"`
}
type DeleteTaskPayload struct {
DeletedTaskID gid.GID `json:"deletedTaskId"`
}
type DeleteVendorInput struct {
VendorID gid.GID `json:"vendorId"`
}

View File

@@ -225,6 +225,18 @@ func (r *mutationResolver) CreateTask(ctx context.Context, input types.CreateTas
}, nil
}
// DeleteTask is the resolver for the deleteTask field.
func (r *mutationResolver) DeleteTask(ctx context.Context, input types.DeleteTaskInput) (*types.DeleteTaskPayload, error) {
err := r.proboSvc.DeleteTask(ctx, input.TaskID)
if err != nil {
return nil, fmt.Errorf("cannot delete task: %w", err)
}
return &types.DeleteTaskPayload{
DeletedTaskID: input.TaskID,
}, nil
}
// Frameworks is the resolver for the frameworks field.
func (r *organizationResolver) Frameworks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.FrameworkConnection, error) {
cursor := types.NewCursor(first, after, last, before)

View File

@@ -269,3 +269,75 @@ WHERE
return nil
}
func (t *Task) Delete(
ctx context.Context,
conn pg.Conn,
scope *Scope,
) error {
// Use a single transaction with conditional logic to handle both cases
q := `
WITH control_count AS (
SELECT COUNT(*) AS count FROM controls_tasks WHERE task_id = @task_id
),
delete_link AS (
DELETE FROM controls_tasks
WHERE task_id = @task_id AND control_id = @control_id
RETURNING task_id
),
delete_transitions AS (
DELETE FROM task_state_transitions
WHERE %s AND task_id = @task_id AND (SELECT count FROM control_count) <= 1
RETURNING task_id
),
delete_all_links AS (
DELETE FROM controls_tasks
WHERE task_id = @task_id AND (SELECT count FROM control_count) <= 1
RETURNING task_id
),
delete_task AS (
DELETE FROM tasks
WHERE %s AND id = @task_id AND (SELECT count FROM control_count) <= 1
RETURNING id
)
SELECT
(SELECT count FROM control_count) AS control_count,
(SELECT COUNT(*) FROM delete_link) AS deleted_links,
(SELECT COUNT(*) FROM delete_transitions) AS deleted_transitions,
(SELECT COUNT(*) FROM delete_all_links) AS deleted_all_links,
(SELECT COUNT(*) FROM delete_task) AS deleted_tasks;
`
q = fmt.Sprintf(q, scope.SQLFragment(), scope.SQLFragment())
args := pgx.NamedArgs{
"task_id": t.ID,
"control_id": t.ControlID,
}
maps.Copy(args, scope.SQLArguments())
var controlCount, deletedLinks, deletedTransitions, deletedAllLinks, deletedTasks int
err := conn.QueryRow(ctx, q, args).Scan(
&controlCount,
&deletedLinks,
&deletedTransitions,
&deletedAllLinks,
&deletedTasks,
)
if err != nil {
return fmt.Errorf("cannot execute delete operation: %w", err)
}
if controlCount <= 1 {
if deletedTransitions == 0 || deletedAllLinks == 0 || deletedTasks == 0 {
return fmt.Errorf("failed to delete task completely: transitions=%d, links=%d, tasks=%d",
deletedTransitions, deletedAllLinks, deletedTasks)
}
} else {
if deletedLinks == 0 {
return fmt.Errorf("failed to delete control-task link")
}
}
return nil
}

22
pkg/probo/delete_task.go Normal file
View File

@@ -0,0 +1,22 @@
package probo
import (
"context"
"github.com/getprobo/probo/pkg/gid"
"github.com/getprobo/probo/pkg/probo/coredata"
"go.gearno.de/kit/pg"
)
func (s Service) DeleteTask(
ctx context.Context,
taskID gid.GID,
) error {
task := coredata.Task{ID: taskID}
return s.pg.WithConn(
ctx,
func(conn pg.Conn) error {
return task.Delete(ctx, conn, s.scope)
},
)
}