[MM-65657] Conditional results db-only (#9135)

* database and types

* transformers

* fix schema version

* fix schema tests

* add missing condition_reason

* add missing tests
This commit is contained in:
Guillermo Vayá 2025-09-22 14:07:25 +02:00 committed by GitHub
parent d4fb05d4bb
commit c193dbd255
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 177 additions and 3 deletions

View file

@ -25,6 +25,18 @@ const {
const {PLAYBOOK_RUN, PLAYBOOK_CHECKLIST, PLAYBOOK_CHECKLIST_ITEM} = PLAYBOOK_TABLES;
export default schemaMigrations({migrations: [
{
toVersion: 13,
steps: [
addColumns({
table: PLAYBOOK_CHECKLIST_ITEM,
columns: [
{name: 'condition_action', type: 'string'},
{name: 'condition_reason', type: 'string'},
],
}),
],
},
{
toVersion: 12,
steps: [

View file

@ -45,7 +45,7 @@ import {
} from './table_schemas';
export const serverSchema: AppSchema = appSchema({
version: 12,
version: 13,
tables: [
CategorySchema,
CategoryChannelSchema,

View file

@ -52,7 +52,7 @@ const {PLAYBOOK_RUN, PLAYBOOK_CHECKLIST, PLAYBOOK_CHECKLIST_ITEM} = PLAYBOOK_TAB
describe('*** Test schema for SERVER database ***', () => {
it('=> The SERVER SCHEMA should strictly match', () => {
expect(serverSchema).toEqual({
version: 12,
version: 13,
unsafeSql: undefined,
tables: {
[CATEGORY]: {
@ -527,6 +527,8 @@ describe('*** Test schema for SERVER database ***', () => {
due_date: {name: 'due_date', type: 'number'},
completed_at: {name: 'completed_at', type: 'number'},
task_actions: {name: 'task_actions', type: 'string', isOptional: true}, // JSON string
condition_action: {name: 'condition_action', type: 'string'},
condition_reason: {name: 'condition_reason', type: 'string'},
sync: {name: 'sync', type: 'string', isIndexed: true, isOptional: true},
last_sync_at: {name: 'last_sync_at', type: 'number', isOptional: true},
update_at: {name: 'update_at', type: 'number'},
@ -544,6 +546,8 @@ describe('*** Test schema for SERVER database ***', () => {
{name: 'due_date', type: 'number'},
{name: 'completed_at', type: 'number'},
{name: 'task_actions', type: 'string', isOptional: true}, // JSON string
{name: 'condition_action', type: 'string'},
{name: 'condition_reason', type: 'string'},
{name: 'sync', type: 'string', isIndexed: true, isOptional: true},
{name: 'last_sync_at', type: 'number', isOptional: true},
{name: 'update_at', type: 'number'},

View file

@ -70,6 +70,12 @@ export default class PlaybookChecklistItemModel extends Model implements Playboo
/** task_actions : The JSON string representing the task actions */
@json('task_actions', safeParseJSONStringArray) taskActions!: TaskAction[];
/** condition_action : The condition action for the checklist item */
@field('condition_action') conditionAction!: ConditionAction;
/** condition_reason : The condition reason for the checklist item, an empty string if there isn't a condition attached */
@field('condition_reason') conditionReason!: string;
/** sync : The sync status of the checklist item */
@field('sync') sync!: SyncStatus;

View file

@ -477,6 +477,8 @@ describe('*** PLAYBOOK_CHECKLIST_ITEM Prepare Records Test ***', () => {
due_date: 1620000003000,
completed_at: 0,
task_actions: [],
condition_action: '',
condition_reason: '',
update_at: 0,
},
},
@ -508,6 +510,8 @@ describe('*** PLAYBOOK_CHECKLIST_ITEM Prepare Records Test ***', () => {
record.dueDate = 1620000003000;
record.completedAt = 0;
record.taskActions = [];
record.conditionAction = '';
record.conditionReason = '';
record.updateAt = 0;
});
});
@ -531,6 +535,8 @@ describe('*** PLAYBOOK_CHECKLIST_ITEM Prepare Records Test ***', () => {
due_date: 1620000007000,
completed_at: 1620000008000,
task_actions: [],
condition_action: 'hidden',
condition_reason: 'test reason',
update_at: 0,
},
},
@ -599,6 +605,8 @@ describe('*** PLAYBOOK_CHECKLIST_ITEM Prepare Records Test ***', () => {
record.dueDate = 1620000003000;
record.completedAt = 0;
record.taskActions = [];
record.conditionAction = '';
record.conditionReason = '';
record.updateAt = 0;
record.lastSyncAt = 1620000003000;
});
@ -641,4 +649,128 @@ describe('*** PLAYBOOK_CHECKLIST_ITEM Prepare Records Test ***', () => {
expect(preparedRecord!.updateAt).toBe(1620000004000);
expect(preparedRecord!.lastSyncAt).toBeGreaterThan(lastSyncAt);
});
it('=> transformPlaybookChecklistItemRecord: should handle condition_action field correctly', async () => {
expect.assertions(6);
const database = await createTestConnection({databaseName: 'playbook_checklist_item_condition_action', setActive: true});
expect(database).toBeTruthy();
// Test CREATE with empty condition_action
const preparedRecordEmpty = await transformPlaybookChecklistItemRecord({
action: OperationType.CREATE,
database: database!,
value: {
record: undefined,
raw: {
id: 'checklist_item_empty',
checklist_id: 'checklist_1',
title: 'Test Item',
condition_action: '',
update_at: 0,
},
},
});
expect(preparedRecordEmpty).toBeTruthy();
expect(preparedRecordEmpty!.conditionAction).toBe('');
// Test CREATE with 'hidden' condition_action
const preparedRecordHidden = await transformPlaybookChecklistItemRecord({
action: OperationType.CREATE,
database: database!,
value: {
record: undefined,
raw: {
id: 'checklist_item_hidden',
checklist_id: 'checklist_1',
title: 'Test Hidden Item',
condition_action: 'hidden',
update_at: 0,
},
},
});
expect(preparedRecordHidden).toBeTruthy();
expect(preparedRecordHidden!.conditionAction).toBe('hidden');
// Test CREATE without condition_action (should default to empty string)
const preparedRecordDefault = await transformPlaybookChecklistItemRecord({
action: OperationType.CREATE,
database: database!,
value: {
record: undefined,
raw: {
id: 'checklist_item_default',
checklist_id: 'checklist_1',
title: 'Test Default Item',
update_at: 0,
},
},
});
expect(preparedRecordDefault!.conditionAction).toBe('');
});
it('=> transformPlaybookChecklistItemRecord: should handle condition_reason field correctly', async () => {
expect.assertions(6);
const database = await createTestConnection({databaseName: 'playbook_checklist_item_condition_reason', setActive: true});
expect(database).toBeTruthy();
// Test CREATE with condition_reason
const preparedRecordWithReason = await transformPlaybookChecklistItemRecord({
action: OperationType.CREATE,
database: database!,
value: {
record: undefined,
raw: {
id: 'checklist_item_with_reason',
checklist_id: 'checklist_1',
title: 'Test Item with Reason',
condition_reason: 'some reason text',
update_at: 0,
},
},
});
expect(preparedRecordWithReason).toBeTruthy();
expect(preparedRecordWithReason!.conditionReason).toBe('some reason text');
// Test CREATE with empty condition_reason
const preparedRecordEmpty = await transformPlaybookChecklistItemRecord({
action: OperationType.CREATE,
database: database!,
value: {
record: undefined,
raw: {
id: 'checklist_item_empty_reason',
checklist_id: 'checklist_1',
title: 'Test Item Empty Reason',
condition_reason: '',
update_at: 0,
},
},
});
expect(preparedRecordEmpty).toBeTruthy();
expect(preparedRecordEmpty!.conditionReason).toBe('');
// Test CREATE without condition_reason (should default to empty string)
const preparedRecordDefault = await transformPlaybookChecklistItemRecord({
action: OperationType.CREATE,
database: database!,
value: {
record: undefined,
raw: {
id: 'checklist_item_default_reason',
checklist_id: 'checklist_1',
title: 'Test Default Reason Item',
update_at: 0,
},
},
});
expect(preparedRecordDefault!.conditionReason).toBe('');
});
});

View file

@ -126,6 +126,8 @@ export const transformPlaybookChecklistItemRecord = ({action, database, value}:
item.dueDate = raw.due_date ?? record?.dueDate ?? 0;
item.completedAt = raw.completed_at ?? record?.completedAt ?? 0;
item.taskActions = raw.task_actions ?? record?.taskActions ?? [];
item.conditionAction = raw.condition_action ?? record?.conditionAction ?? '';
item.conditionReason = raw.condition_reason ?? record?.conditionReason ?? '';
item.updateAt = raw.update_at ?? record?.updateAt ?? 0;
item.lastSyncAt = Date.now();
};

View file

@ -22,6 +22,8 @@ export default tableSchema({
{name: 'due_date', type: 'number'},
{name: 'completed_at', type: 'number'},
{name: 'task_actions', type: 'string', isOptional: true}, // JSON string
{name: 'condition_action', type: 'string'},
{name: 'condition_reason', type: 'string'},
{name: 'sync', type: 'string', isIndexed: true, isOptional: true},
{name: 'last_sync_at', type: 'number', isOptional: true},
{name: 'update_at', type: 'number'},

View file

@ -3,6 +3,8 @@
type ChecklistItemState = '' | 'in_progress' | 'closed' | 'skipped';
type ConditionAction = '' | 'hidden';
const PlaybookRunStatus = {
InProgress: 'InProgress',
Finished: 'Finished',
@ -30,6 +32,8 @@ type PlaybookChecklistItem = {
command_last_run: number;
due_date: number;
task_actions?: TaskAction[];
condition_action?: ConditionAction;
condition_reason?: string;
completed_at: number;
update_at: number;
}

View file

@ -59,6 +59,12 @@ declare class PlaybookChecklistItemModel extends Model {
// JSON string representing the task actions
taskActions: TaskAction[];
// The condition action for the checklist item
conditionAction: ConditionAction;
// The condition reason for the checklist item
conditionReason: string;
// The timestamp when the checklist item was updated
updateAt: number;

View file

@ -1,4 +1,4 @@
# Server Database - Schema Version 10
# Server Database - Schema Version 13
# Please bump the version by 1, any time the schema changes.
# Also, include the migration plan under app/database/migration/server,
# update all models, relationships and types.
@ -223,6 +223,8 @@ command string
command_last_run number
due_date number
task_actions string # stringified array of TaskAction
condition_action string # condition action for the checklist item (values should be '' or 'hidden')
condition_reason string # condition reason for the checklist item (default empty string meaning there is no condition affecting)
order number
completed_at number
synced string NULL INDEX # optional field for sync status

View file

@ -1080,6 +1080,8 @@ class TestHelperSingleton {
due_date: 0,
completed_at: 0,
task_actions: [],
condition_action: '',
condition_reason: '',
update_at: 0,
});
@ -1253,6 +1255,8 @@ class TestHelperSingleton {
sync: 'synced',
lastSyncAt: 0,
taskActions: [],
conditionAction: '',
conditionReason: '',
checklist: this.fakeRelation(),
updateAt: 0,
...overwrite,