diff --git a/apps/console/src/_locales/en-US.json b/apps/console/src/_locales/en-US.json
index 32b4325473..32d92afcd2 100644
--- a/apps/console/src/_locales/en-US.json
+++ b/apps/console/src/_locales/en-US.json
@@ -766,14 +766,14 @@
"timeEstimate": { "label": "Time estimate" },
"deadline": { "label": "Deadline" }
},
- "states": { "todo": "To do", "inProgress": "In progress", "done": "Done" },
+ "states": { "backlog": "Backlog", "todo": "To do", "inProgress": "In progress", "done": "Done", "canceled": "Canceled", "duplicate": "Duplicate" },
"priorities": { "urgent": "Urgent", "high": "High", "medium": "Medium", "low": "Low" },
"messages": { "created": "Task created successfully.", "updated": "Task updated successfully." },
"errors": { "create": "Failed to create task", "update": "Failed to update task" },
"actions": { "create": "Create task", "update": "Update task" }
},
"tasksCard": {
- "states": { "all": "All", "todo": "To do", "inProgress": "In progress", "done": "Done" },
+ "states": { "all": "All", "backlog": "Backlog", "todo": "To do", "inProgress": "In progress", "done": "Done", "canceled": "Canceled", "duplicate": "Duplicate" },
"empty": "No tasks",
"error": { "title": "Error", "reorder": "Failed to reorder task." },
"dragInstructions": {
@@ -781,7 +781,7 @@
"state": "Drag and drop to reorder tasks"
},
"deleteConfirmation": "Are you sure you want to delete this task?",
- "actions": { "moveToInProgress": "Move to In progress", "moveToDone": "Move to Done", "edit": "Edit", "delete": "Delete" }
+ "actions": { "moveToTodo": "Move to To do", "moveToInProgress": "Move to In progress", "moveToDone": "Move to Done", "edit": "Edit", "delete": "Delete" }
},
"deleteTrustCenterReferenceDialog": {
"title": "Delete Reference",
diff --git a/apps/console/src/_locales/fr-FR.json b/apps/console/src/_locales/fr-FR.json
index 749917fd96..cfd950984b 100644
--- a/apps/console/src/_locales/fr-FR.json
+++ b/apps/console/src/_locales/fr-FR.json
@@ -1297,7 +1297,10 @@
"states": {
"todo": "À faire",
"inProgress": "En cours",
- "done": "Terminé"
+ "done": "Terminé",
+ "backlog": "Backlog",
+ "canceled": "Annulé",
+ "duplicate": "Doublon"
},
"priorities": {
"urgent": "Urgente",
@@ -1323,7 +1326,10 @@
"all": "Toutes",
"todo": "À faire",
"inProgress": "En cours",
- "done": "Terminé"
+ "done": "Terminé",
+ "backlog": "Backlog",
+ "canceled": "Annulé",
+ "duplicate": "Doublon"
},
"empty": "Aucune tâche",
"error": {
@@ -1336,6 +1342,7 @@
},
"deleteConfirmation": "Voulez-vous vraiment supprimer cette tâche ?",
"actions": {
+ "moveToTodo": "Déplacer vers À faire",
"moveToInProgress": "Déplacer vers En cours",
"moveToDone": "Déplacer vers Terminé",
"edit": "Modifier",
diff --git a/apps/console/src/_locales/nl-NL.json b/apps/console/src/_locales/nl-NL.json
index 1c48236e30..a4e8f2edff 100644
--- a/apps/console/src/_locales/nl-NL.json
+++ b/apps/console/src/_locales/nl-NL.json
@@ -1297,7 +1297,10 @@
"states": {
"todo": "Te doen",
"inProgress": "In uitvoering",
- "done": "Voltooid"
+ "done": "Voltooid",
+ "backlog": "Backlog",
+ "canceled": "Geannuleerd",
+ "duplicate": "Duplicaat"
},
"priorities": {
"urgent": "Urgent",
@@ -1323,7 +1326,10 @@
"all": "Alle",
"todo": "Te doen",
"inProgress": "In uitvoering",
- "done": "Voltooid"
+ "done": "Voltooid",
+ "backlog": "Backlog",
+ "canceled": "Geannuleerd",
+ "duplicate": "Duplicaat"
},
"empty": "Geen taken",
"error": {
@@ -1336,6 +1342,7 @@
},
"deleteConfirmation": "Weet u zeker dat u deze taak wilt verwijderen?",
"actions": {
+ "moveToTodo": "Verplaatsen naar Te doen",
"moveToInProgress": "Verplaatsen naar In uitvoering",
"moveToDone": "Verplaatsen naar Voltooid",
"edit": "Bewerken",
diff --git a/apps/console/src/components/tasks/TaskFormDialog.tsx b/apps/console/src/components/tasks/TaskFormDialog.tsx
index 70a85f0b38..f441757fc0 100644
--- a/apps/console/src/components/tasks/TaskFormDialog.tsx
+++ b/apps/console/src/components/tasks/TaskFormDialog.tsx
@@ -99,12 +99,13 @@ export const taskUpdateMutation = graphql`
}
`;
-export const taskStates = ["TODO", "IN_PROGRESS", "DONE"] as const;
+export const taskStates = ["BACKLOG", "TODO", "IN_PROGRESS", "DONE", "CANCELED", "DUPLICATE"] as const;
export const taskPriorities = ["URGENT", "HIGH", "MEDIUM", "LOW"] as const;
const createTaskSchema = z.object({
name: z.string().min(1),
description: z.string().optional().nullable(),
+ state: z.enum(taskStates),
priority: z.enum(taskPriorities),
timeEstimate: z.string().optional().nullable(),
assignedToId: z.string().optional().nullable(),
@@ -217,6 +218,7 @@ export default function TaskFormDialog(props: Props) {
organizationId,
name: data.name,
description: data.description || null,
+ state: "state" in data ? data.state : undefined,
priority: data.priority,
timeEstimate: data.timeEstimate || null,
deadline: formatDatetime(data.deadline) ?? null,
@@ -271,42 +273,58 @@ export default function TaskFormDialog(props: Props) {
{/* Properties form */}
- {isUpdating && (
-
- (
-
- )}
- />
-
- )}
+
+ (
+
+ )}
+ />
+
{
+ const renderTaskRow = (node: (typeof tasks)[number]["node"], sectionState?: TaskState) => {
const task = readTask(node);
return (
= {
+ BACKLOG: { state: "TODO", label: t("tasksCard.actions.moveToTodo"), icon: IconRadioUnchecked, className: "text-txt-quaternary" },
TODO: { state: "IN_PROGRESS", label: t("tasksCard.actions.moveToInProgress"), icon: IconCircleProgress, className: "text-txt-warning" },
IN_PROGRESS: { state: "DONE", label: t("tasksCard.actions.moveToDone"), icon: IconCircleCheck, className: "text-txt-accent" },
};
diff --git a/e2e/console/task_test.go b/e2e/console/task_test.go
index 97d602f613..2d57114e57 100644
--- a/e2e/console/task_test.go
+++ b/e2e/console/task_test.go
@@ -41,6 +41,7 @@ func TestTask_Create(t *testing.T) {
node {
id
name
+ state
}
}
}
@@ -51,8 +52,9 @@ func TestTask_Create(t *testing.T) {
CreateTask struct {
TaskEdge struct {
Node struct {
- ID string `json:"id"`
- Name string `json:"name"`
+ ID string `json:"id"`
+ Name string `json:"name"`
+ State string `json:"state"`
} `json:"node"`
} `json:"taskEdge"`
} `json:"createTask"`
@@ -72,6 +74,7 @@ func TestTask_Create(t *testing.T) {
task := result.CreateTask.TaskEdge.Node
assert.NotEmpty(t, task.ID)
assert.Equal(t, "Owner Task", task.Name)
+ assert.Equal(t, "TODO", task.State)
}
func TestTask_CreateWithoutMeasure(t *testing.T) {
@@ -327,12 +330,53 @@ func TestTask_StateEnum(t *testing.T) {
Create()
states := []string{
+ "BACKLOG",
"TODO",
"IN_PROGRESS",
"DONE",
+ "CANCELED",
+ "DUPLICATE",
}
for _, state := range states {
+ t.Run("create with state "+state, func(t *testing.T) {
+ query := `
+ mutation CreateTask($input: CreateTaskInput!) {
+ createTask(input: $input) {
+ taskEdge {
+ node {
+ id
+ state
+ }
+ }
+ }
+ }
+ `
+
+ var result struct {
+ CreateTask struct {
+ TaskEdge struct {
+ Node struct {
+ ID string `json:"id"`
+ State string `json:"state"`
+ } `json:"node"`
+ } `json:"taskEdge"`
+ } `json:"createTask"`
+ }
+
+ err := owner.Execute(query, map[string]any{
+ "input": map[string]any{
+ "organizationId": owner.GetOrganizationID().String(),
+ "measureId": measureID,
+ "name": "Create State " + state,
+ "priority": "MEDIUM",
+ "state": state,
+ },
+ }, &result)
+ require.NoError(t, err, "State %s should be valid on create", state)
+ assert.Equal(t, state, result.CreateTask.TaskEdge.Node.State)
+ })
+
t.Run("update to state "+state, func(t *testing.T) {
taskID := factory.NewTask(owner, measureID).
WithName("State Test " + state).
diff --git a/packages/n8n-node/nodes/Probo/actions/task/create.operation.ts b/packages/n8n-node/nodes/Probo/actions/task/create.operation.ts
index c81140148d..c826321f8e 100644
--- a/packages/n8n-node/nodes/Probo/actions/task/create.operation.ts
+++ b/packages/n8n-node/nodes/Probo/actions/task/create.operation.ts
@@ -76,6 +76,45 @@ export const description: INodeProperties[] = [
default: '',
description: 'The description of the task',
},
+ {
+ displayName: 'State',
+ name: 'state',
+ type: 'options',
+ displayOptions: {
+ show: {
+ resource: ['task'],
+ operation: ['create'],
+ },
+ },
+ options: [
+ {
+ name: 'Backlog',
+ value: 'BACKLOG',
+ },
+ {
+ name: 'Canceled',
+ value: 'CANCELED',
+ },
+ {
+ name: 'Done',
+ value: 'DONE',
+ },
+ {
+ name: 'Duplicate',
+ value: 'DUPLICATE',
+ },
+ {
+ name: 'In Progress',
+ value: 'IN_PROGRESS',
+ },
+ {
+ name: 'Todo',
+ value: 'TODO',
+ },
+ ],
+ default: 'TODO',
+ description: 'The state of the task',
+ },
{
displayName: 'Priority',
name: 'priority',
@@ -156,6 +195,7 @@ export async function execute(
const measureId = this.getNodeParameter('measureId', itemIndex, '') as string;
const name = this.getNodeParameter('name', itemIndex) as string;
const description = this.getNodeParameter('description', itemIndex, '') as string;
+ const state = this.getNodeParameter('state', itemIndex, '') as string;
const priority = this.getNodeParameter('priority', itemIndex, '') as string;
const timeEstimate = this.getNodeParameter('timeEstimate', itemIndex, '') as string;
const assignedToId = this.getNodeParameter('assignedToId', itemIndex, '') as string;
@@ -187,6 +227,7 @@ export async function execute(
name,
...(measureId && { measureId }),
...(description && { description }),
+ ...(state && { state }),
...(priority && { priority }),
...(timeEstimate && { timeEstimate }),
...(assignedToId && { assignedToId }),
diff --git a/packages/n8n-node/nodes/Probo/actions/task/update.operation.ts b/packages/n8n-node/nodes/Probo/actions/task/update.operation.ts
index 75de5bc97f..081dc6be38 100644
--- a/packages/n8n-node/nodes/Probo/actions/task/update.operation.ts
+++ b/packages/n8n-node/nodes/Probo/actions/task/update.operation.ts
@@ -78,17 +78,29 @@ export const description: INodeProperties[] = [
value: '',
},
{
- name: 'Todo',
- value: 'TODO',
+ name: 'Backlog',
+ value: 'BACKLOG',
},
{
- name: 'In Progress',
- value: 'IN_PROGRESS',
+ name: 'Canceled',
+ value: 'CANCELED',
},
{
name: 'Done',
value: 'DONE',
},
+ {
+ name: 'Duplicate',
+ value: 'DUPLICATE',
+ },
+ {
+ name: 'In Progress',
+ value: 'IN_PROGRESS',
+ },
+ {
+ name: 'Todo',
+ value: 'TODO',
+ },
],
default: '',
description: 'The state of the task',
diff --git a/packages/ui/src/Atoms/Icons/IconCircleSlashes.tsx b/packages/ui/src/Atoms/Icons/IconCircleSlashes.tsx
new file mode 100644
index 0000000000..745823ef38
--- /dev/null
+++ b/packages/ui/src/Atoms/Icons/IconCircleSlashes.tsx
@@ -0,0 +1,33 @@
+// Copyright (c) 2026 Probo Inc .
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+import type { IconProps } from "./type";
+
+export function IconCircleSlashes({ size = 24, className }: IconProps) {
+ return (
+
+ );
+}
diff --git a/packages/ui/src/Atoms/Icons/TaskStateIcon.stories.tsx b/packages/ui/src/Atoms/Icons/TaskStateIcon.stories.tsx
index 5d9c4bc3f4..8038f0c1b4 100644
--- a/packages/ui/src/Atoms/Icons/TaskStateIcon.stories.tsx
+++ b/packages/ui/src/Atoms/Icons/TaskStateIcon.stories.tsx
@@ -30,6 +30,12 @@ export default {
type Story = StoryObj;
+export const Backlog: Story = {
+ args: {
+ state: "BACKLOG",
+ },
+};
+
export const Default: Story = {
args: {
state: "TODO",
@@ -47,3 +53,15 @@ export const Done: Story = {
state: "DONE",
},
};
+
+export const Canceled: Story = {
+ args: {
+ state: "CANCELED",
+ },
+};
+
+export const Duplicate: Story = {
+ args: {
+ state: "DUPLICATE",
+ },
+};
diff --git a/packages/ui/src/Atoms/Icons/TaskStateIcon.tsx b/packages/ui/src/Atoms/Icons/TaskStateIcon.tsx
index 652cd208e7..85ec72a283 100644
--- a/packages/ui/src/Atoms/Icons/TaskStateIcon.tsx
+++ b/packages/ui/src/Atoms/Icons/TaskStateIcon.tsx
@@ -18,21 +18,33 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
+import { CircleDashedIcon } from "@phosphor-icons/react";
+
import { IconCircleCheck } from "./IconCircleCheck";
import { IconCircleProgress } from "./IconCircleProgress";
+import { IconCircleSlashes } from "./IconCircleSlashes";
+import { IconCircleX } from "./IconCircleX";
import { IconRadioUnchecked } from "./IconRadioUnchecked";
+export type TaskState = "BACKLOG" | "TODO" | "IN_PROGRESS" | "DONE" | "CANCELED" | "DUPLICATE";
+
type Props = {
- state: "TODO" | "IN_PROGRESS" | "DONE";
+ state: TaskState;
};
export function TaskStateIcon({ state }: Props) {
switch (state) {
+ case "BACKLOG":
+ return ;
case "TODO":
return ;
case "IN_PROGRESS":
return ;
case "DONE":
return ;
+ case "CANCELED":
+ return ;
+ case "DUPLICATE":
+ return ;
}
}
diff --git a/packages/ui/src/Atoms/Icons/index.tsx b/packages/ui/src/Atoms/Icons/index.tsx
index 94733e4785..0e85193682 100644
--- a/packages/ui/src/Atoms/Icons/index.tsx
+++ b/packages/ui/src/Atoms/Icons/index.tsx
@@ -25,6 +25,7 @@ export { IconBell2 } from "./IconBell2";
export { IconMedal } from "./IconMedal";
export { IconMail } from "./IconMail";
export { IconArrowInbox1 } from "./IconArrowInbox1";
+export { IconCircleSlashes } from "./IconCircleSlashes";
export { IconCircleX } from "./IconCircleX";
export { IconFire3 } from "./IconFire3";
export { IconMagnifyingGlass } from "./IconMagnifyingGlass";
diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts
index 98ba8fe972..b095d7c569 100644
--- a/packages/ui/src/index.ts
+++ b/packages/ui/src/index.ts
@@ -67,6 +67,7 @@ export { ControlItem } from "./Atoms/ControlItem/ControlItem";
export { InfiniteScrollTrigger } from "./Atoms/InfiniteScrollTrigger/InfiniteScrollTrigger";
export { PriorityLevel } from "./Atoms/PriorityLevel/PriorityLevel";
export { TaskStateIcon } from "./Atoms/Icons/TaskStateIcon";
+export type { TaskState } from "./Atoms/Icons/TaskStateIcon";
export { Checkbox } from "./Atoms/Checkbox/Checkbox";
export { Toggle } from "./Atoms/Toggle/Toggle";
export {
diff --git a/pkg/cmd/cmdutil/flags.go b/pkg/cmd/cmdutil/flags.go
index d24eb52ec5..c075b52f18 100644
--- a/pkg/cmd/cmdutil/flags.go
+++ b/pkg/cmd/cmdutil/flags.go
@@ -33,6 +33,21 @@ const (
OutputTable = "table"
)
+var (
+ TaskStates = []string{
+ "BACKLOG",
+ "TODO",
+ "IN_PROGRESS",
+ "DONE",
+ "CANCELED",
+ "DUPLICATE",
+ }
+)
+
+func TaskStateFlagUsage() string {
+ return "Task state: " + strings.Join(TaskStates, ", ")
+}
+
// AddOutputFlag registers --output / -o on cmd and returns a pointer to the
// value. The default is "table". Callers should call ValidateOutputFlag early
// in RunE, then branch on *p.
diff --git a/pkg/cmd/task/create/create.go b/pkg/cmd/task/create/create.go
index 99be758da9..7a65e3932b 100644
--- a/pkg/cmd/task/create/create.go
+++ b/pkg/cmd/task/create/create.go
@@ -63,6 +63,7 @@ func NewCmdCreate(f *cmdutil.Factory) *cobra.Command {
flagOrg string
flagName string
flagDescription string
+ flagState string
flagPriority string
flagMeasure string
flagTimeEstimate string
@@ -146,6 +147,14 @@ func NewCmdCreate(f *cmdutil.Factory) *cobra.Command {
input["description"] = flagDescription
}
+ if flagState != "" {
+ if err := cmdutil.ValidateEnum("state", flagState, cmdutil.TaskStates); err != nil {
+ return err
+ }
+
+ input["state"] = flagState
+ }
+
if flagPriority != "" {
input["priority"] = flagPriority
}
@@ -194,6 +203,7 @@ func NewCmdCreate(f *cmdutil.Factory) *cobra.Command {
cmd.Flags().StringVar(&flagOrg, "org", "", "Organization ID")
cmd.Flags().StringVar(&flagName, "name", "", "Task name (required)")
cmd.Flags().StringVar(&flagDescription, "description", "", "Task description")
+ cmd.Flags().StringVar(&flagState, "state", "", cmdutil.TaskStateFlagUsage())
cmd.Flags().StringVar(&flagPriority, "priority", "", "Task priority: URGENT, HIGH, MEDIUM, LOW")
cmd.Flags().StringVar(&flagMeasure, "measure", "", "Measure ID")
cmd.Flags().StringVar(&flagTimeEstimate, "time-estimate", "", "Time estimate")
diff --git a/pkg/cmd/task/update/update.go b/pkg/cmd/task/update/update.go
index 1fe54841fe..01725c2a5e 100644
--- a/pkg/cmd/task/update/update.go
+++ b/pkg/cmd/task/update/update.go
@@ -101,6 +101,10 @@ func NewCmdUpdate(f *cmdutil.Factory) *cobra.Command {
}
if cmd.Flags().Changed("state") {
+ if err := cmdutil.ValidateEnum("state", flagState, cmdutil.TaskStates); err != nil {
+ return err
+ }
+
input["state"] = flagState
}
@@ -163,7 +167,7 @@ func NewCmdUpdate(f *cmdutil.Factory) *cobra.Command {
cmd.Flags().StringVar(&flagName, "name", "", "Task name")
cmd.Flags().StringVar(&flagDescription, "description", "", "Task description")
- cmd.Flags().StringVar(&flagState, "state", "", "Task state: TODO, IN_PROGRESS, DONE")
+ cmd.Flags().StringVar(&flagState, "state", "", cmdutil.TaskStateFlagUsage())
cmd.Flags().StringVar(&flagPriority, "priority", "", "Task priority: URGENT, HIGH, MEDIUM, LOW")
cmd.Flags().StringVar(&flagTimeEstimate, "time-estimate", "", "Time estimate")
cmd.Flags().StringVar(&flagDeadline, "deadline", "", "Deadline")
diff --git a/pkg/coredata/migrations/20260828T133808Z.sql b/pkg/coredata/migrations/20260828T133808Z.sql
new file mode 100644
index 0000000000..badb3e3fe4
--- /dev/null
+++ b/pkg/coredata/migrations/20260828T133808Z.sql
@@ -0,0 +1,23 @@
+-- Copyright (c) 2026 Probo Inc .
+--
+-- Permission is hereby granted, free of charge, to any person obtaining a copy
+-- of this software and associated documentation files (the "Software"), to deal
+-- in the Software without restriction, including without limitation the rights
+-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+-- copies of the Software, and to permit persons to whom the Software is
+-- furnished to do so, subject to the following conditions:
+--
+-- The above copyright notice and this permission notice shall be included in
+-- all copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+-- SOFTWARE.
+
+ALTER TYPE task_state ADD VALUE 'BACKLOG' BEFORE 'TODO';
+ALTER TYPE task_state ADD VALUE 'CANCELED';
+ALTER TYPE task_state ADD VALUE 'DUPLICATE';
diff --git a/pkg/coredata/task_state.go b/pkg/coredata/task_state.go
index c590da85c5..fc76b54449 100644
--- a/pkg/coredata/task_state.go
+++ b/pkg/coredata/task_state.go
@@ -30,9 +30,12 @@ type (
)
const (
+ TaskStateBacklog TaskState = "BACKLOG"
TaskStateTodo TaskState = "TODO"
TaskStateInProgress TaskState = "IN_PROGRESS"
TaskStateDone TaskState = "DONE"
+ TaskStateCanceled TaskState = "CANCELED"
+ TaskStateDuplicate TaskState = "DUPLICATE"
)
var (
@@ -43,18 +46,24 @@ var (
func TaskStates() []TaskState {
return []TaskState{
+ TaskStateBacklog,
TaskStateTodo,
TaskStateInProgress,
TaskStateDone,
+ TaskStateCanceled,
+ TaskStateDuplicate,
}
}
func (v TaskState) IsValid() bool {
switch v {
case
+ TaskStateBacklog,
TaskStateTodo,
TaskStateInProgress,
- TaskStateDone:
+ TaskStateDone,
+ TaskStateCanceled,
+ TaskStateDuplicate:
return true
}
diff --git a/pkg/probo/task_service.go b/pkg/probo/task_service.go
index 7450a91b0a..b601a25103 100644
--- a/pkg/probo/task_service.go
+++ b/pkg/probo/task_service.go
@@ -44,6 +44,7 @@ type (
MeasureID *gid.GID
Name string
Description *string
+ State *coredata.TaskState
Priority coredata.TaskPriority
TimeEstimate *time.Duration
AssignedToID *gid.GID
@@ -71,6 +72,7 @@ func (ctr *CreateTaskRequest) Validate() error {
v.Check(ctr.MeasureID, "measure_id", validator.GID(coredata.MeasureEntityType))
v.Check(ctr.Name, "name", validator.SafeTextNoNewLine(TitleMaxLength))
v.Check(ctr.Description, "description", validator.SafeText(ContentMaxLength))
+ v.Check(ctr.State, "state", validator.OneOfSlice(coredata.TaskStates()))
v.Check(ctr.Priority, "priority", validator.Required(), validator.OneOfSlice(coredata.TaskPriorities()))
v.Check(ctr.TimeEstimate, "time_estimate", validator.RangeDuration(0, 1000*time.Hour))
v.Check(ctr.AssignedToID, "assigned_to_id", validator.GID(coredata.MembershipProfileEntityType))
@@ -110,6 +112,11 @@ func (s TaskService) Create(
return nil, fmt.Errorf("cannot generate reference id: %w", err)
}
+ state := coredata.TaskStateTodo
+ if req.State != nil {
+ state = *req.State
+ }
+
task := &coredata.Task{
ID: taskID,
OrganizationID: req.OrganizationID,
@@ -120,7 +127,7 @@ func (s TaskService) Create(
TimeEstimate: req.TimeEstimate,
AssignedToID: req.AssignedToID,
Deadline: req.Deadline,
- State: coredata.TaskStateTodo,
+ State: state,
ReferenceID: "custom-task-" + referenceID.String(),
CreatedAt: now,
UpdatedAt: now,
diff --git a/pkg/server/api/console/v1/graphql/task.graphql b/pkg/server/api/console/v1/graphql/task.graphql
index 80d41524d1..730f3f0309 100644
--- a/pkg/server/api/console/v1/graphql/task.graphql
+++ b/pkg/server/api/console/v1/graphql/task.graphql
@@ -1,7 +1,10 @@
enum TaskState @goModel(model: "go.probo.inc/probo/pkg/coredata.TaskState") {
+ BACKLOG @goEnum(value: "go.probo.inc/probo/pkg/coredata.TaskStateBacklog")
TODO @goEnum(value: "go.probo.inc/probo/pkg/coredata.TaskStateTodo")
IN_PROGRESS @goEnum(value: "go.probo.inc/probo/pkg/coredata.TaskStateInProgress")
DONE @goEnum(value: "go.probo.inc/probo/pkg/coredata.TaskStateDone")
+ CANCELED @goEnum(value: "go.probo.inc/probo/pkg/coredata.TaskStateCanceled")
+ DUPLICATE @goEnum(value: "go.probo.inc/probo/pkg/coredata.TaskStateDuplicate")
}
enum TaskPriority
@@ -91,6 +94,7 @@ input CreateTaskInput {
measureId: ID
name: String!
description: String
+ state: TaskState
priority: TaskPriority!
timeEstimate: Duration
assignedToId: ID
diff --git a/pkg/server/api/console/v1/task_resolvers.go b/pkg/server/api/console/v1/task_resolvers.go
index 96169e5517..e0001e768e 100644
--- a/pkg/server/api/console/v1/task_resolvers.go
+++ b/pkg/server/api/console/v1/task_resolvers.go
@@ -36,6 +36,7 @@ func (r *mutationResolver) CreateTask(ctx context.Context, input types.CreateTas
OrganizationID: input.OrganizationID,
Name: input.Name,
Description: input.Description,
+ State: input.State,
Priority: input.Priority,
TimeEstimate: input.TimeEstimate,
AssignedToID: input.AssignedToID,
diff --git a/pkg/server/api/mcp/v1/schema.resolvers.go b/pkg/server/api/mcp/v1/schema.resolvers.go
index ad44e35dc6..e9d62ac7f1 100644
--- a/pkg/server/api/mcp/v1/schema.resolvers.go
+++ b/pkg/server/api/mcp/v1/schema.resolvers.go
@@ -2133,6 +2133,7 @@ func (r *Resolver) AddTaskTool(ctx context.Context, req *mcp.CallToolRequest, in
MeasureID: input.MeasureID,
Name: input.Name,
Description: input.Description,
+ State: input.State,
Priority: priority,
TimeEstimate: input.TimeEstimate,
Deadline: input.Deadline,
diff --git a/pkg/server/api/mcp/v1/specification.yaml b/pkg/server/api/mcp/v1/specification.yaml
index 54b9c8ca6b..cadb503a0e 100644
--- a/pkg/server/api/mcp/v1/specification.yaml
+++ b/pkg/server/api/mcp/v1/specification.yaml
@@ -6871,9 +6871,12 @@ components:
TaskState:
type: string
enum:
+ - BACKLOG
- TODO
- IN_PROGRESS
- DONE
+ - CANCELED
+ - DUPLICATE
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.TaskState
TaskOrderField:
@@ -7069,6 +7072,10 @@ components:
- type: "null"
description: Not assigned
description: Assigned to person ID
+ state:
+ $ref: "#/components/schemas/TaskState"
+ description: Task state (defaults to TODO if not specified)
+ default: TODO
priority:
$ref: "#/components/schemas/TaskPriority"
description: Task priority level (defaults to MEDIUM if not specified)