feat: definition of done per project
A project can ask that tickets reach a DONE status only with their checklist ticked (settings.checklistBeforeDone, off by default; absent when saving means unchanged). Members get a 422 telling how many steps are left; owners and admins may still close it. Settings has the switch and the ticket's checklist says when the project asks for it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1511,4 +1511,60 @@ describe('task-stream (e2e)', () => {
|
||||
);
|
||||
expect((await rest('token-ana', 'GET', '/me/views')).body).toEqual([]);
|
||||
});
|
||||
|
||||
it('keeps unfinished checklists out of DONE when the project asks', async () => {
|
||||
const key = 'H' + Date.now().toString(36).slice(-5).toUpperCase();
|
||||
await rest('token-ana', 'POST', '/projects', { key, name: key });
|
||||
const project = await eventually(
|
||||
() => rest('token-ana', 'GET', `/projects/${key}`),
|
||||
(r) => r.status === 200,
|
||||
);
|
||||
expect(project.body['settings'].checklistBeforeDone).toBe(false);
|
||||
await rest('token-ana', 'POST', `/projects/${key}/members`, {
|
||||
userId: '102',
|
||||
});
|
||||
expect(
|
||||
(
|
||||
await rest('token-ana', 'PUT', `/projects/${key}/settings`, {
|
||||
...project.body['settings'],
|
||||
checklistBeforeDone: true,
|
||||
})
|
||||
).status,
|
||||
).toBe(200);
|
||||
const issue = (
|
||||
await rest('token-bob', 'POST', `/projects/${key}/issues`, {
|
||||
type: 'TASK',
|
||||
title: 'Con pasos',
|
||||
})
|
||||
).body['key'];
|
||||
await eventually(
|
||||
() => rest('token-bob', 'GET', `/issues/${issue}`),
|
||||
(r) => r.status === 200,
|
||||
);
|
||||
await rest('token-bob', 'POST', `/issues/${issue}/checklist`, {
|
||||
text: 'Probar en Safari',
|
||||
});
|
||||
for (const status of [
|
||||
'TODO',
|
||||
'IN_PROGRESS',
|
||||
'IN_REVIEW',
|
||||
'READY_FOR_QA',
|
||||
'IN_QA',
|
||||
])
|
||||
await rest('token-bob', 'PUT', `/issues/${issue}/status`, { status });
|
||||
|
||||
const refused = await rest('token-bob', 'PUT', `/issues/${issue}/status`, {
|
||||
status: 'DONE',
|
||||
});
|
||||
expect(refused.status).toBe(422);
|
||||
expect(refused.body['message']).toMatch(/queda 1 paso/);
|
||||
// The owner may close it anyway.
|
||||
expect(
|
||||
(
|
||||
await rest('token-ana', 'PUT', `/issues/${issue}/status`, {
|
||||
status: 'DONE',
|
||||
})
|
||||
).status,
|
||||
).toBe(200);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10982,6 +10982,10 @@
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/CustomFieldDefinitionDto"
|
||||
}
|
||||
},
|
||||
"checklistBeforeDone": {
|
||||
"type": "boolean",
|
||||
"description": "Definition of done: tickets reach DONE only with their checklist ticked"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -10989,7 +10993,8 @@
|
||||
"transitions",
|
||||
"issueTypes",
|
||||
"areas",
|
||||
"customFields"
|
||||
"customFields",
|
||||
"checklistBeforeDone"
|
||||
]
|
||||
},
|
||||
"ProjectDto": {
|
||||
@@ -11379,6 +11384,10 @@
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/CustomFieldDefinitionBodyDto"
|
||||
}
|
||||
},
|
||||
"checklistBeforeDone": {
|
||||
"type": "boolean",
|
||||
"description": "Definition of done: tickets reach a DONE status only with their checklist ticked (owners and admins may override). Absent: unchanged"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
||||
@@ -1185,6 +1185,11 @@ Statuses (board order), transitions, ticket types, areas and custom fields
|
||||
"""
|
||||
type ProjectSettings {
|
||||
areas: [AreaOption!]!
|
||||
|
||||
"""
|
||||
Definition of done: tickets reach DONE only with their checklist ticked (owners and admins may override)
|
||||
"""
|
||||
checklistBeforeDone: Boolean!
|
||||
customFields: [CustomFieldDefinition!]!
|
||||
issueTypes: [IssueTypeOption!]!
|
||||
statuses: [WorkflowStatus!]!
|
||||
@@ -1197,6 +1202,11 @@ The whole configuration (replaces the previous one). Statuses go in board order.
|
||||
input ProjectSettingsInput {
|
||||
areas: [AreaOptionInput!]!
|
||||
|
||||
"""
|
||||
Definition of done: DONE only with the checklist ticked. Absent: unchanged
|
||||
"""
|
||||
checklistBeforeDone: Boolean
|
||||
|
||||
"""Absent: unchanged. Fields cannot be removed: archive them."""
|
||||
customFields: [CustomFieldDefinitionInput!]
|
||||
issueTypes: [IssueTypeOptionInput!]!
|
||||
|
||||
Reference in New Issue
Block a user