Skip to content

Commit cf85d22

Browse files
Checklist (#9234) (#9284)
(cherry picked from commit be5b6c1) Co-authored-by: Guillermo Vayá <[email protected]>
1 parent 4a2cad6 commit cf85d22

File tree

65 files changed

+1472
-175
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+1472
-175
lines changed

app/components/navigation_header/header.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export type HeaderRightButton = {
1717
buttonType?: 'native' | 'opacity' | 'highlight';
1818
color?: string;
1919
iconName: string;
20-
count?: number;
20+
count?: number | string;
2121
onPress: () => void;
2222
rippleRadius?: number;
2323
testID?: string;

app/database/migration/server/index.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,17 @@ const {
2525
const {PLAYBOOK_RUN, PLAYBOOK_CHECKLIST, PLAYBOOK_CHECKLIST_ITEM, PLAYBOOK_RUN_ATTRIBUTE, PLAYBOOK_RUN_ATTRIBUTE_VALUE} = PLAYBOOK_TABLES;
2626

2727
export default schemaMigrations({migrations: [
28+
{
29+
toVersion: 15,
30+
steps: [
31+
addColumns({
32+
table: PLAYBOOK_RUN,
33+
columns: [
34+
{name: 'type', type: 'string'},
35+
],
36+
}),
37+
],
38+
},
2839
{
2940
toVersion: 14,
3041
steps: [

app/database/schema/server/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ import {
4545
} from './table_schemas';
4646

4747
export const serverSchema: AppSchema = appSchema({
48-
version: 14,
48+
version: 15,
4949
tables: [
5050
CategorySchema,
5151
CategoryChannelSchema,

app/database/schema/server/test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ const {PLAYBOOK_RUN, PLAYBOOK_CHECKLIST, PLAYBOOK_CHECKLIST_ITEM, PLAYBOOK_RUN_A
5252
describe('*** Test schema for SERVER database ***', () => {
5353
it('=> The SERVER SCHEMA should strictly match', () => {
5454
expect(serverSchema).toEqual({
55-
version: 14,
55+
version: 15,
5656
unsafeSql: undefined,
5757
tables: {
5858
[CATEGORY]: {
@@ -463,6 +463,7 @@ describe('*** Test schema for SERVER database ***', () => {
463463
items_order: {name: 'items_order', type: 'string'},
464464
previous_reminder: {name: 'previous_reminder', type: 'number', isOptional: true},
465465
update_at: {name: 'update_at', type: 'number'},
466+
type: {name: 'type', type: 'string'},
466467
},
467468
columnArray: [
468469
{name: 'playbook_id', type: 'string'},
@@ -489,6 +490,7 @@ describe('*** Test schema for SERVER database ***', () => {
489490
{name: 'previous_reminder', type: 'number', isOptional: true},
490491
{name: 'items_order', type: 'string'},
491492
{name: 'update_at', type: 'number'},
493+
{name: 'type', type: 'string'},
492494
],
493495
},
494496
[PLAYBOOK_CHECKLIST]: {

app/products/playbooks/actions/local/checklist.test.ts

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22
// See LICENSE.txt for license information.
33

44
import DatabaseManager from '@database/manager';
5+
import {getPlaybookChecklistById} from '@playbooks/database/queries/checklist';
56
import {getPlaybookChecklistItemById} from '@playbooks/database/queries/item';
67
import TestHelper from '@test/test_helper';
78

8-
import {updateChecklistItem, setChecklistItemCommand, setAssignee, setDueDate} from './checklist';
9+
import {updateChecklistItem, setChecklistItemCommand, setAssignee, setDueDate, renameChecklist} from './checklist';
910

1011
import type ServerDataOperator from '@database/operator/server_data_operator';
1112

@@ -264,3 +265,109 @@ describe('setDueDate', () => {
264265
expect(updated!.dueDate).toBe(0);
265266
});
266267
});
268+
269+
describe('renameChecklist', () => {
270+
it('should handle not found database', async () => {
271+
const {error} = await renameChecklist('foo', 'checklistid', 'New Title');
272+
expect(error).toBeTruthy();
273+
expect((error as Error).message).toContain('foo database not found');
274+
});
275+
276+
it('should handle checklist not found', async () => {
277+
const {error} = await renameChecklist(serverUrl, 'nonexistent', 'New Title');
278+
expect(error).toBe('Checklist not found: nonexistent');
279+
});
280+
281+
it('should handle database write errors', async () => {
282+
const runId = 'runid';
283+
const checklist = {
284+
...TestHelper.createPlaybookChecklist(runId, 0, 0),
285+
run_id: runId,
286+
order: 0,
287+
};
288+
await operator.handlePlaybookChecklist({checklists: [checklist], prepareRecordsOnly: false});
289+
290+
const originalWrite = operator.database.write;
291+
operator.database.write = jest.fn().mockRejectedValue(new Error('Database write failed'));
292+
293+
const {error} = await renameChecklist(serverUrl, checklist.id, 'New Title');
294+
expect(error).toBeTruthy();
295+
296+
operator.database.write = originalWrite;
297+
});
298+
299+
it('should rename checklist successfully', async () => {
300+
const runId = 'runid';
301+
const checklist = {
302+
...TestHelper.createPlaybookChecklist(runId, 0, 0),
303+
run_id: runId,
304+
order: 0,
305+
};
306+
await operator.handlePlaybookChecklist({checklists: [checklist], prepareRecordsOnly: false});
307+
308+
const newTitle = 'Updated Checklist Title';
309+
const {data, error} = await renameChecklist(serverUrl, checklist.id, newTitle);
310+
expect(error).toBeUndefined();
311+
expect(data).toBe(true);
312+
313+
const updated = await getPlaybookChecklistById(operator.database, checklist.id);
314+
expect(updated).toBeDefined();
315+
expect(updated!.title).toBe(newTitle);
316+
});
317+
318+
it('should handle empty title string', async () => {
319+
const runId = 'runid';
320+
const checklist = {
321+
...TestHelper.createPlaybookChecklist(runId, 0, 0),
322+
run_id: runId,
323+
order: 0,
324+
};
325+
await operator.handlePlaybookChecklist({checklists: [checklist], prepareRecordsOnly: false});
326+
327+
const {data, error} = await renameChecklist(serverUrl, checklist.id, '');
328+
expect(error).toBeUndefined();
329+
expect(data).toBe(true);
330+
331+
const updated = await getPlaybookChecklistById(operator.database, checklist.id);
332+
expect(updated).toBeDefined();
333+
expect(updated!.title).toBe('');
334+
});
335+
336+
it('should handle whitespace-only title', async () => {
337+
const runId = 'runid';
338+
const checklist = {
339+
...TestHelper.createPlaybookChecklist(runId, 0, 0),
340+
run_id: runId,
341+
order: 0,
342+
};
343+
await operator.handlePlaybookChecklist({checklists: [checklist], prepareRecordsOnly: false});
344+
345+
const whitespaceTitle = ' ';
346+
const {data, error} = await renameChecklist(serverUrl, checklist.id, whitespaceTitle);
347+
expect(error).toBeUndefined();
348+
expect(data).toBe(true);
349+
350+
const updated = await getPlaybookChecklistById(operator.database, checklist.id);
351+
expect(updated).toBeDefined();
352+
expect(updated!.title).toBe(whitespaceTitle);
353+
});
354+
355+
it('should handle very long titles', async () => {
356+
const runId = 'runid';
357+
const checklist = {
358+
...TestHelper.createPlaybookChecklist(runId, 0, 0),
359+
run_id: runId,
360+
order: 0,
361+
};
362+
await operator.handlePlaybookChecklist({checklists: [checklist], prepareRecordsOnly: false});
363+
364+
const longTitle = 'A'.repeat(300); // 300 characters
365+
const {data, error} = await renameChecklist(serverUrl, checklist.id, longTitle);
366+
expect(error).toBeUndefined();
367+
expect(data).toBe(true);
368+
369+
const updated = await getPlaybookChecklistById(operator.database, checklist.id);
370+
expect(updated).toBeDefined();
371+
expect(updated!.title).toBe(longTitle);
372+
});
373+
});

app/products/playbooks/actions/local/checklist.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// See LICENSE.txt for license information.
33

44
import DatabaseManager from '@database/manager';
5+
import {getPlaybookChecklistById} from '@playbooks/database/queries/checklist';
56
import {getPlaybookChecklistItemById} from '@playbooks/database/queries/item';
67
import {logError} from '@utils/log';
78

@@ -88,3 +89,24 @@ export async function setDueDate(serverUrl: string, itemId: string, date?: numbe
8889
return {error};
8990
}
9091
}
92+
93+
export async function renameChecklist(serverUrl: string, checklistId: string, title: string) {
94+
try {
95+
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
96+
const checklist = await getPlaybookChecklistById(database, checklistId);
97+
if (!checklist) {
98+
return {error: `Checklist not found: ${checklistId}`};
99+
}
100+
101+
await database.write(async () => {
102+
checklist.update((c) => {
103+
c.title = title;
104+
});
105+
});
106+
107+
return {data: true};
108+
} catch (error) {
109+
logError('failed to rename checklist', error);
110+
return {error};
111+
}
112+
}

app/products/playbooks/actions/local/run.test.ts

Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import DatabaseManager from '@database/manager';
55
import {PLAYBOOK_TABLES} from '@playbooks/constants/database';
66
import TestHelper from '@test/test_helper';
77

8-
import {handlePlaybookRuns, setOwner} from './run';
8+
import {handlePlaybookRuns, setOwner, renamePlaybookRun} from './run';
99

1010
import type {Database} from '@nozbe/watermelondb';
1111
import type PlaybookRunModel from '@playbooks/types/database/models/playbook_run';
@@ -80,7 +80,7 @@ describe('setOwner', () => {
8080

8181
const {data, error} = await setOwner(serverUrl, nonExistentRunId, newOwnerId);
8282

83-
expect(error).toBe('Run not found');
83+
expect(error).toBe('Checklist not found');
8484
expect(data).toBeUndefined();
8585
});
8686

@@ -153,3 +153,100 @@ describe('setOwner', () => {
153153
expect(updatedRun.ownerUserId).toBe(emptyOwnerId);
154154
});
155155
});
156+
157+
describe('renamePlaybookRun', () => {
158+
let database: Database;
159+
beforeEach(() => {
160+
database = DatabaseManager.getServerDatabaseAndOperator(serverUrl).database;
161+
});
162+
163+
it('should handle not found database', async () => {
164+
const {error} = await renamePlaybookRun('foo', 'runid', 'New Name');
165+
expect(error).toBeTruthy();
166+
expect((error as Error).message).toContain('foo database not found');
167+
});
168+
169+
it('should handle playbook run not found', async () => {
170+
const {error} = await renamePlaybookRun(serverUrl, 'nonexistent', 'New Name');
171+
expect(error).toBe('Playbook run not found: nonexistent');
172+
});
173+
174+
it('should handle database write errors', async () => {
175+
const runs = TestHelper.createPlaybookRuns(1, 0, 0);
176+
await handlePlaybookRuns(serverUrl, runs, false, false);
177+
178+
const playbookRunId = runs[0].id;
179+
180+
const originalWrite = database.write;
181+
database.write = jest.fn().mockRejectedValue(new Error('Database write failed'));
182+
183+
const {error} = await renamePlaybookRun(serverUrl, playbookRunId, 'New Name');
184+
expect(error).toBeTruthy();
185+
expect((error as Error).message).toBe('Database write failed');
186+
187+
database.write = originalWrite;
188+
});
189+
190+
it('should rename playbook run successfully', async () => {
191+
const runs = TestHelper.createPlaybookRuns(1, 0, 0);
192+
await handlePlaybookRuns(serverUrl, runs, false, false);
193+
194+
const playbookRunId = runs[0].id;
195+
const newName = 'Updated Run Name';
196+
197+
const {data, error} = await renamePlaybookRun(serverUrl, playbookRunId, newName);
198+
199+
expect(error).toBeUndefined();
200+
expect(data).toBe(true);
201+
202+
const updatedRun = await database.get<PlaybookRunModel>(PLAYBOOK_TABLES.PLAYBOOK_RUN).find(playbookRunId);
203+
expect(updatedRun.name).toBe(newName);
204+
});
205+
206+
it('should handle empty name string', async () => {
207+
const runs = TestHelper.createPlaybookRuns(1, 0, 0);
208+
await handlePlaybookRuns(serverUrl, runs, false, false);
209+
210+
const playbookRunId = runs[0].id;
211+
212+
const {data, error} = await renamePlaybookRun(serverUrl, playbookRunId, '');
213+
214+
expect(error).toBeUndefined();
215+
expect(data).toBe(true);
216+
217+
const updatedRun = await database.get<PlaybookRunModel>(PLAYBOOK_TABLES.PLAYBOOK_RUN).find(playbookRunId);
218+
expect(updatedRun.name).toBe('');
219+
});
220+
221+
it('should handle whitespace-only name', async () => {
222+
const runs = TestHelper.createPlaybookRuns(1, 0, 0);
223+
await handlePlaybookRuns(serverUrl, runs, false, false);
224+
225+
const playbookRunId = runs[0].id;
226+
const whitespaceName = ' ';
227+
228+
const {data, error} = await renamePlaybookRun(serverUrl, playbookRunId, whitespaceName);
229+
230+
expect(error).toBeUndefined();
231+
expect(data).toBe(true);
232+
233+
const updatedRun = await database.get<PlaybookRunModel>(PLAYBOOK_TABLES.PLAYBOOK_RUN).find(playbookRunId);
234+
expect(updatedRun.name).toBe(whitespaceName);
235+
});
236+
237+
it('should handle very long names', async () => {
238+
const runs = TestHelper.createPlaybookRuns(1, 0, 0);
239+
await handlePlaybookRuns(serverUrl, runs, false, false);
240+
241+
const playbookRunId = runs[0].id;
242+
const longName = 'A'.repeat(300); // 300 characters
243+
244+
const {data, error} = await renamePlaybookRun(serverUrl, playbookRunId, longName);
245+
246+
expect(error).toBeUndefined();
247+
expect(data).toBe(true);
248+
249+
const updatedRun = await database.get<PlaybookRunModel>(PLAYBOOK_TABLES.PLAYBOOK_RUN).find(playbookRunId);
250+
expect(updatedRun.name).toBe(longName);
251+
});
252+
});

app/products/playbooks/actions/local/run.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export async function handlePlaybookRuns(serverUrl: string, runs: PlaybookRun[],
1515
});
1616
return {data};
1717
} catch (error) {
18-
logError('failed to handle playbook runs', error);
18+
logError('failed to handle playbook checklist', error);
1919
return {error};
2020
}
2121
}
@@ -25,7 +25,7 @@ export async function setOwner(serverUrl: string, playbookRunId: string, ownerId
2525
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
2626
const run = await getPlaybookRunById(database, playbookRunId);
2727
if (!run) {
28-
return {error: 'Run not found'};
28+
return {error: 'Checklist not found'};
2929
}
3030

3131
await database.write(async () => {
@@ -40,3 +40,24 @@ export async function setOwner(serverUrl: string, playbookRunId: string, ownerId
4040
return {error};
4141
}
4242
}
43+
44+
export async function renamePlaybookRun(serverUrl: string, playbookRunId: string, name: string) {
45+
try {
46+
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
47+
const run = await getPlaybookRunById(database, playbookRunId);
48+
if (!run) {
49+
return {error: `Playbook run not found: ${playbookRunId}`};
50+
}
51+
52+
await database.write(async () => {
53+
run.update((r) => {
54+
r.name = name;
55+
});
56+
});
57+
58+
return {data: true};
59+
} catch (error) {
60+
logError('failed to rename playbook run', error);
61+
return {error};
62+
}
63+
}

0 commit comments

Comments
 (0)