Address plugin changes around incremental websocket (#9011)

* Address plugin changes around incremental websocket

* ensure change_fields is set and is an object before processing

---------

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
This commit is contained in:
Daniel Espino García 2025-07-24 04:16:52 +02:00 committed by GitHub
parent 7086158e76
commit 2be07ec6d0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 213 additions and 397 deletions

View file

@ -5,8 +5,6 @@ import {WebsocketEvents} from '@constants';
import {WEBSOCKET_EVENTS} from '@playbooks/constants/websocket';
import {
handlePlaybookChecklistItemUpdated,
handlePlaybookChecklistUpdated,
handlePlaybookRunCreated,
handlePlaybookRunUpdated,
handlePlaybookRunUpdatedIncremental,
@ -30,12 +28,6 @@ export async function handlePlaybookEvents(serverUrl: string, msg: WebSocketMess
case WEBSOCKET_EVENTS.WEBSOCKET_PLAYBOOK_RUN_UPDATED_INCREMENTAL:
handlePlaybookRunUpdatedIncremental(serverUrl, msg);
break;
case WEBSOCKET_EVENTS.WEBSOCKET_PLAYBOOK_CHECKLIST_UPDATED:
handlePlaybookChecklistUpdated(serverUrl, msg);
break;
case WEBSOCKET_EVENTS.WEBSOCKET_PLAYBOOK_CHECKLIST_ITEM_UPDATED:
handlePlaybookChecklistItemUpdated(serverUrl, msg);
break;
default:
break;
}

View file

@ -3,7 +3,6 @@
import DatabaseManager from '@database/manager';
import {handlePlaybookRuns} from '@playbooks/actions/local/run';
import {getPlaybookChecklistById} from '@playbooks/database/queries/checklist';
import {getPlaybookRunById} from '@playbooks/database/queries/run';
import EphemeralStore from '@store/ephemeral_store';
import TestHelper from '@test/test_helper';
@ -12,8 +11,6 @@ import {
handlePlaybookRunCreated,
handlePlaybookRunUpdated,
handlePlaybookRunUpdatedIncremental,
handlePlaybookChecklistUpdated,
handlePlaybookChecklistItemUpdated,
} from './runs';
import type ServerDataOperator from '@database/operator/server_data_operator';
@ -26,7 +23,6 @@ mockPlaybookRun.update_at = 1; // Set the update_at to a value that is before th
const channelId = mockPlaybookRun.channel_id;
const playbookRunId = mockPlaybookRun.id;
const checklistId = mockPlaybookRun.checklists[0].id;
const checklistItemId = mockPlaybookRun.checklists[0].items[0].id;
jest.mock('@playbooks/actions/local/run');
@ -131,379 +127,221 @@ describe('handlePlaybookRunUpdated', () => {
describe('handlePlaybookRunUpdatedIncremental', () => {
let spyHandlePlaybookRun: jest.SpyInstance;
let spyHandlePlaybookChecklist: jest.SpyInstance;
let spyHandlePlaybookChecklistItem: jest.SpyInstance;
let spyBatchRecords: jest.SpyInstance;
const mockPlaybookRunUpdate: PlaybookRunUpdate = {
id: playbookRunId,
playbook_run_updated_at: Date.now(),
changed_fields: {
name: 'Updated Run Name',
description: 'Updated description',
},
};
function createFakeUpdateFromRun(run: PlaybookRun) {
const {checklists, ...restRun} = run;
const fakeUpdate: PlaybookRunUpdate = {
id: run.id,
playbook_run_updated_at: Date.now(),
changed_fields: {
...restRun,
checklists: checklists.map((checklist) => {
const {items, ...restChecklist} = checklist;
return {
id: checklist.id,
checklist_updated_at: Date.now(),
fields: restChecklist,
item_inserts: items,
};
}),
},
};
const mockWebSocketMessage = TestHelper.fakeWebsocketMessage({
data: {
payload: JSON.stringify(mockPlaybookRunUpdate),
},
});
return fakeUpdate;
}
function clearSpies() {
spyHandlePlaybookRun.mockClear();
spyHandlePlaybookChecklist.mockClear();
spyHandlePlaybookChecklistItem.mockClear();
spyBatchRecords.mockClear();
}
function expectSpiesNotCalled() {
expect(spyHandlePlaybookRun).not.toHaveBeenCalled();
expect(spyHandlePlaybookChecklist).not.toHaveBeenCalled();
expect(spyHandlePlaybookChecklistItem).not.toHaveBeenCalled();
expect(spyBatchRecords).not.toHaveBeenCalled();
}
beforeEach(() => {
spyHandlePlaybookRun = jest.spyOn(operator, 'handlePlaybookRun');
spyHandlePlaybookChecklist = jest.spyOn(operator, 'handlePlaybookChecklist');
spyHandlePlaybookChecklistItem = jest.spyOn(operator, 'handlePlaybookChecklistItem');
spyBatchRecords = jest.spyOn(operator, 'batchRecords');
});
it('should return early when no payload', async () => {
const msg = {...mockWebSocketMessage, data: {payload: undefined}};
const msg = TestHelper.fakeWebsocketMessage({
data: {payload: undefined},
});
await handlePlaybookRunUpdatedIncremental(serverUrl, msg);
expect(spyHandlePlaybookRun).not.toHaveBeenCalled();
expectSpiesNotCalled();
});
it('should return early when payload cannot be parsed', async () => {
const msg = {...mockWebSocketMessage, data: {payload: '{{'}};
await handlePlaybookRunUpdatedIncremental(serverUrl, msg);
expect(spyHandlePlaybookRun).not.toHaveBeenCalled();
});
it('should return early when changed_fields is missing', async () => {
const msg = TestHelper.fakeWebsocketMessage({
data: {
payload: JSON.stringify({
id: playbookRunId,
updated_at: Date.now(),
}),
},
data: {payload: '{{'},
});
await handlePlaybookRunUpdatedIncremental(serverUrl, msg);
expect(spyHandlePlaybookRun).not.toHaveBeenCalled();
expectSpiesNotCalled();
});
it('should return early when run is not in database', async () => {
await handlePlaybookRunUpdatedIncremental(serverUrl, mockWebSocketMessage);
const update = createFakeUpdateFromRun(mockPlaybookRun);
const msg = TestHelper.fakeWebsocketMessage({
data: {payload: JSON.stringify(update)},
});
expect(spyHandlePlaybookRun).not.toHaveBeenCalled();
await handlePlaybookRunUpdatedIncremental(serverUrl, msg);
expectSpiesNotCalled();
});
it('should return early when channel is not synced', async () => {
const update = createFakeUpdateFromRun(mockPlaybookRun);
const msg = TestHelper.fakeWebsocketMessage({
data: {payload: JSON.stringify(update)},
});
await operator.handlePlaybookRun({
prepareRecordsOnly: false,
runs: mockPlaybookList,
});
spyHandlePlaybookRun.mockClear();
clearSpies();
jest.mocked(EphemeralStore.getChannelPlaybooksSynced).mockReturnValue(false);
await handlePlaybookRunUpdatedIncremental(serverUrl, mockWebSocketMessage);
await handlePlaybookRunUpdatedIncremental(serverUrl, msg);
expect(EphemeralStore.getChannelPlaybooksSynced).toHaveBeenCalledWith(serverUrl, channelId);
expect(spyHandlePlaybookRun).not.toHaveBeenCalled();
expectSpiesNotCalled();
});
it('should handle playbook run incremental update successfully', async () => {
it('should handle incremental update successfully', async () => {
await operator.handlePlaybookRun({
prepareRecordsOnly: false,
runs: mockPlaybookList,
processChildren: true,
});
clearSpies();
const update = createFakeUpdateFromRun(mockPlaybookRun);
update.changed_fields.name = 'Updated Run Name';
update.changed_fields.description = 'Updated description';
update.changed_fields.checklists![0].fields!.title = 'Updated Checklist';
update.changed_fields.checklists![0].item_inserts = [];
update.changed_fields.checklists![0].item_updates = [{id: checklistItemId, checklist_item_updated_at: Date.now(), fields: {title: 'Updated Item'}}];
const msg = TestHelper.fakeWebsocketMessage({
data: {payload: JSON.stringify(update)},
});
spyHandlePlaybookRun.mockClear();
jest.mocked(EphemeralStore.getChannelPlaybooksSynced).mockReturnValue(true);
await handlePlaybookRunUpdatedIncremental(serverUrl, mockWebSocketMessage);
await handlePlaybookRunUpdatedIncremental(serverUrl, msg);
expect(EphemeralStore.getChannelPlaybooksSynced).toHaveBeenCalledWith(serverUrl, channelId);
expect(spyHandlePlaybookRun).toHaveBeenCalled();
const storedRun = await getPlaybookRunById(operator.database, playbookRunId);
expect(storedRun?.name).toEqual(mockPlaybookRunUpdate.changed_fields.name);
expect(storedRun?.description).toEqual(mockPlaybookRunUpdate.changed_fields.description);
expect(storedRun?.updateAt).toEqual(mockPlaybookRunUpdate.playbook_run_updated_at);
expect(storedRun?.name).toEqual(update.changed_fields.name);
expect(storedRun?.description).toEqual(update.changed_fields.description);
expect(storedRun?.updateAt).toEqual(update.playbook_run_updated_at);
const checklists = await storedRun!.checklists.fetch();
expect(checklists).toHaveLength(1);
expect(checklists[0].title).toEqual(update.changed_fields.checklists![0].fields!.title);
const items = await checklists[0].items.fetch();
expect(items).toHaveLength(1);
expect(items[0].title).toEqual(update.changed_fields.checklists![0].item_updates![0].fields!.title);
});
it('should not add checklists even if they are present in the payload', async () => {
it('should handle incremental update without changes in the run', async () => {
await operator.handlePlaybookRun({
prepareRecordsOnly: false,
runs: mockPlaybookList,
processChildren: false,
processChildren: true,
});
spyHandlePlaybookRun.mockClear();
clearSpies();
jest.mocked(EphemeralStore.getChannelPlaybooksSynced).mockReturnValue(true);
const msg = TestHelper.fakeWebsocketMessage({
data: {
payload: JSON.stringify({
id: playbookRunId,
updated_at: Date.now(),
changed_fields: {
checklists: {
...mockPlaybookRun.checklists[0],
name: 'Updated Checklist',
},
},
}),
},
});
await handlePlaybookRunUpdatedIncremental(serverUrl, msg);
expect(spyHandlePlaybookRun).toHaveBeenCalled();
const checklist = await getPlaybookChecklistById(operator.database, mockPlaybookRun.checklists[0].id);
expect(checklist).toBeUndefined();
});
});
describe('handlePlaybookChecklistUpdated', () => {
let spyHandlePlaybookChecklist: jest.SpyInstance;
let spyHandlePlaybookChecklistItem: jest.SpyInstance;
const mockChecklistUpdate: PlaybookChecklistUpdate = {
id: checklistId,
index: 0,
checklist_updated_at: Date.now(),
fields: {
title: 'Updated Checklist',
},
item_inserts: [
TestHelper.fakePlaybookChecklistItem(checklistId, {id: checklistItemId}),
],
};
const mockPlaybookChecklistUpdatePayload: PlaybookChecklistUpdatePayload = {
playbook_run_id: playbookRunId,
update: mockChecklistUpdate,
};
const mockWebSocketMessage = TestHelper.fakeWebsocketMessage({
data: {
payload: JSON.stringify(mockPlaybookChecklistUpdatePayload),
},
});
beforeEach(() => {
spyHandlePlaybookChecklist = jest.spyOn(operator, 'handlePlaybookChecklist');
spyHandlePlaybookChecklistItem = jest.spyOn(operator, 'handlePlaybookChecklistItem');
});
it('should return early when no payload', async () => {
const msg = {...mockWebSocketMessage, data: {payload: undefined}};
await handlePlaybookChecklistUpdated(serverUrl, msg);
expect(spyHandlePlaybookChecklist).not.toHaveBeenCalled();
expect(spyHandlePlaybookChecklistItem).not.toHaveBeenCalled();
});
it('should return early when payload cannot be parsed', async () => {
const msg = TestHelper.fakeWebsocketMessage({
data: {
payload: '{{',
},
});
await handlePlaybookChecklistUpdated(serverUrl, msg);
expect(spyHandlePlaybookChecklist).not.toHaveBeenCalled();
expect(spyHandlePlaybookChecklistItem).not.toHaveBeenCalled();
});
it('should return early when update.fields is missing', async () => {
const msg = TestHelper.fakeWebsocketMessage({
data: {
payload: JSON.stringify({
playbook_run_id: playbookRunId,
update: {id: checklistId, index: 0, updated_at: Date.now()},
}),
},
});
await handlePlaybookChecklistUpdated(serverUrl, msg);
expect(spyHandlePlaybookChecklist).not.toHaveBeenCalled();
expect(spyHandlePlaybookChecklistItem).not.toHaveBeenCalled();
});
it('should return early when run is not in database', async () => {
await handlePlaybookChecklistUpdated(serverUrl, mockWebSocketMessage);
expect(spyHandlePlaybookChecklist).not.toHaveBeenCalled();
expect(spyHandlePlaybookChecklistItem).not.toHaveBeenCalled();
});
it('should return early when channel is not synced', async () => {
await operator.handlePlaybookRun({
prepareRecordsOnly: false,
runs: mockPlaybookList,
});
jest.mocked(EphemeralStore.getChannelPlaybooksSynced).mockReturnValue(false);
await handlePlaybookChecklistUpdated(serverUrl, mockWebSocketMessage);
expect(EphemeralStore.getChannelPlaybooksSynced).toHaveBeenCalledWith(serverUrl, channelId);
expect(spyHandlePlaybookChecklist).not.toHaveBeenCalled();
expect(spyHandlePlaybookChecklistItem).not.toHaveBeenCalled();
});
it('should handle checklist update successfully without item inserts', async () => {
await operator.handlePlaybookRun({
prepareRecordsOnly: false,
runs: mockPlaybookList,
});
const payloadWithoutInserts = {
...mockPlaybookChecklistUpdatePayload,
update: {
...mockChecklistUpdate,
item_inserts: undefined,
},
const update = createFakeUpdateFromRun(mockPlaybookRun);
update.changed_fields = {
checklists: update.changed_fields.checklists,
};
const msg = TestHelper.fakeWebsocketMessage({
data: {
payload: JSON.stringify(payloadWithoutInserts),
},
data: {payload: JSON.stringify(update)},
});
jest.mocked(EphemeralStore.getChannelPlaybooksSynced).mockReturnValue(true);
await handlePlaybookChecklistUpdated(serverUrl, msg);
await handlePlaybookRunUpdatedIncremental(serverUrl, msg);
expect(EphemeralStore.getChannelPlaybooksSynced).toHaveBeenCalledWith(serverUrl, channelId);
expect(spyHandlePlaybookChecklist).toHaveBeenCalledWith({
checklists: [{
...mockChecklistUpdate.fields,
items: undefined,
id: checklistId,
update_at: mockChecklistUpdate.checklist_updated_at,
run_id: playbookRunId,
}],
prepareRecordsOnly: false,
processChildren: false,
});
expect(spyHandlePlaybookChecklistItem).not.toHaveBeenCalled();
expect(spyHandlePlaybookRun).not.toHaveBeenCalled();
expect(spyHandlePlaybookChecklist).toHaveBeenCalled();
expect(spyHandlePlaybookChecklistItem).toHaveBeenCalled();
expect(spyBatchRecords).toHaveBeenCalled();
});
it('should handle checklist update successfully with item inserts', async () => {
it('should handle incremental update without changes in the checklist', async () => {
await operator.handlePlaybookRun({
prepareRecordsOnly: false,
runs: mockPlaybookList,
processChildren: true,
});
clearSpies();
jest.mocked(EphemeralStore.getChannelPlaybooksSynced).mockReturnValue(true);
const update = createFakeUpdateFromRun(mockPlaybookRun);
update.changed_fields.checklists![0].fields = undefined;
await handlePlaybookChecklistUpdated(serverUrl, mockWebSocketMessage);
expect(spyHandlePlaybookChecklist).toHaveBeenCalledWith({
checklists: [{
...mockChecklistUpdate.fields,
items: undefined,
id: checklistId,
update_at: mockChecklistUpdate.checklist_updated_at,
run_id: playbookRunId,
}],
prepareRecordsOnly: false,
processChildren: false,
});
expect(spyHandlePlaybookChecklistItem).toHaveBeenCalledWith({
items: [{
...mockChecklistUpdate.item_inserts![0],
checklist_id: checklistId,
update_at: mockChecklistUpdate.checklist_updated_at,
}],
prepareRecordsOnly: false,
});
});
});
describe('handlePlaybookChecklistItemUpdated', () => {
let spyHandlePlaybookChecklistItem: jest.SpyInstance;
const mockChecklistItemUpdate: PlaybookChecklistItemUpdate = {
id: checklistItemId,
index: 0,
checklist_item_updated_at: Date.now(),
fields: {
title: 'Updated Item',
state: 'closed',
},
};
const mockPlaybookChecklistItemUpdatePayload: PlaybookChecklistItemUpdatePayload = {
playbook_run_id: playbookRunId,
checklist_id: checklistId,
update: mockChecklistItemUpdate,
};
const mockWebSocketMessage = TestHelper.fakeWebsocketMessage({
data: {
payload: JSON.stringify(mockPlaybookChecklistItemUpdatePayload),
},
});
beforeEach(() => {
spyHandlePlaybookChecklistItem = jest.spyOn(operator, 'handlePlaybookChecklistItem');
});
it('should return early when no payload', async () => {
const msg = {...mockWebSocketMessage, data: {payload: undefined}};
await handlePlaybookChecklistItemUpdated(serverUrl, msg);
expect(spyHandlePlaybookChecklistItem).not.toHaveBeenCalled();
});
it('should return early when payload cannot be parsed', async () => {
const msg = TestHelper.fakeWebsocketMessage({
data: {
payload: '{{',
},
});
await handlePlaybookChecklistItemUpdated(serverUrl, msg);
expect(spyHandlePlaybookChecklistItem).not.toHaveBeenCalled();
});
it('should return early when run is not in database', async () => {
await handlePlaybookChecklistItemUpdated(serverUrl, mockWebSocketMessage);
expect(spyHandlePlaybookChecklistItem).not.toHaveBeenCalled();
});
it('should return early when channel is not synced', async () => {
await operator.handlePlaybookRun({
prepareRecordsOnly: false,
runs: mockPlaybookList,
});
jest.mocked(EphemeralStore.getChannelPlaybooksSynced).mockReturnValue(false);
await handlePlaybookChecklistItemUpdated(serverUrl, mockWebSocketMessage);
expect(EphemeralStore.getChannelPlaybooksSynced).toHaveBeenCalledWith(serverUrl, channelId);
expect(spyHandlePlaybookChecklistItem).not.toHaveBeenCalled();
});
it('should handle checklist item update successfully', async () => {
await operator.handlePlaybookRun({
prepareRecordsOnly: false,
runs: mockPlaybookList,
data: {payload: JSON.stringify(update)},
});
jest.mocked(EphemeralStore.getChannelPlaybooksSynced).mockReturnValue(true);
await handlePlaybookChecklistItemUpdated(serverUrl, mockWebSocketMessage);
await handlePlaybookRunUpdatedIncremental(serverUrl, msg);
expect(EphemeralStore.getChannelPlaybooksSynced).toHaveBeenCalledWith(serverUrl, channelId);
expect(spyHandlePlaybookChecklistItem).toHaveBeenCalledWith({
items: [{
...mockChecklistItemUpdate.fields,
id: checklistItemId,
checklist_id: checklistId,
update_at: mockChecklistItemUpdate.checklist_item_updated_at,
}],
expect(spyHandlePlaybookChecklist).not.toHaveBeenCalled();
expect(spyHandlePlaybookRun).toHaveBeenCalled();
expect(spyHandlePlaybookChecklistItem).toHaveBeenCalled();
expect(spyBatchRecords).toHaveBeenCalled();
});
it('should handle incremental update without changes in the checklist item', async () => {
await operator.handlePlaybookRun({
prepareRecordsOnly: false,
runs: mockPlaybookList,
processChildren: true,
});
clearSpies();
const update = createFakeUpdateFromRun(mockPlaybookRun);
update.changed_fields.checklists![0].item_inserts = undefined;
update.changed_fields.checklists![0].item_updates = undefined;
const msg = TestHelper.fakeWebsocketMessage({
data: {payload: JSON.stringify(update)},
});
jest.mocked(EphemeralStore.getChannelPlaybooksSynced).mockReturnValue(true);
await handlePlaybookRunUpdatedIncremental(serverUrl, msg);
expect(spyHandlePlaybookChecklistItem).not.toHaveBeenCalled();
expect(spyHandlePlaybookRun).toHaveBeenCalled();
expect(spyHandlePlaybookChecklist).toHaveBeenCalled();
expect(spyBatchRecords).toHaveBeenCalled();
});
});

View file

@ -7,6 +7,8 @@ import {getPlaybookRunById} from '@playbooks/database/queries/run';
import EphemeralStore from '@store/ephemeral_store';
import {safeParseJSON} from '@utils/helpers';
import type {Model} from '@nozbe/watermelondb';
const isValidEvent = (data: unknown) => {
if (!data || typeof data !== 'object') {
return false;
@ -60,7 +62,7 @@ export const handlePlaybookRunUpdatedIncremental = async (serverUrl: string, msg
return;
}
const data = safeParseJSON(msg.data.payload) as PlaybookRunUpdate;
if (!data?.changed_fields) {
if (!data || !data.changed_fields || typeof data.changed_fields !== 'object') {
return;
}
@ -78,91 +80,89 @@ export const handlePlaybookRunUpdatedIncremental = async (serverUrl: string, msg
return;
}
await operator.handlePlaybookRun({
runs: [{
...data.changed_fields,
checklists: undefined, // Remove the checklists from the update
id: data.id,
update_at: data.playbook_run_updated_at,
}],
prepareRecordsOnly: false,
processChildren: false,
});
const models: Model[] = [];
const hasRunChangedFields = Object.keys(data.changed_fields).filter((key) => key !== 'checklists').length > 0;
if (hasRunChangedFields) {
const runModels = await operator.handlePlaybookRun({
runs: [{
...data.changed_fields,
checklists: undefined, // Remove the checklists from the update
id: data.id,
update_at: data.playbook_run_updated_at,
}],
prepareRecordsOnly: true,
processChildren: false,
});
models.push(...runModels);
}
if (data.changed_fields.checklists) {
const promises = [];
for (const checklist of data.changed_fields.checklists) {
promises.push(handlePlaybookChecklistUpdated(serverUrl, checklist, data.id));
}
const checklistModels = await Promise.all(promises);
models.push(...checklistModels.flat());
}
if (models.length > 0) {
await operator.batchRecords(models, 'handlePlaybookRunUpdatedIncremental');
}
};
export const handlePlaybookChecklistUpdated = async (serverUrl: string, msg: WebSocketMessage) => {
if (!msg.data.payload) {
return;
}
const data = safeParseJSON(msg.data.payload) as PlaybookChecklistUpdatePayload;
if (!data?.update?.fields && !data?.update?.items_order) {
return;
const handlePlaybookChecklistUpdated = async (serverUrl: string, checklistUpdate: PlaybookChecklistUpdate, runId: string) => {
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const models: Model[] = [];
const hasChecklistChangedFields = Object.keys(checklistUpdate.fields || {}).length > 0;
if (hasChecklistChangedFields) {
const checklistModels = await operator.handlePlaybookChecklist({
checklists: [{
...(checklistUpdate.fields || {}),
items_order: checklistUpdate.items_order,
items: undefined, // Remove the items from the update
id: checklistUpdate.id,
update_at: checklistUpdate.checklist_updated_at,
run_id: runId,
}],
prepareRecordsOnly: true,
processChildren: false,
});
models.push(...checklistModels);
}
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const run = await getPlaybookRunById(database, data.playbook_run_id);
if (!run) {
// Do not handle any data if the run is not in the database
return;
}
const isSynced = EphemeralStore.getChannelPlaybooksSynced(serverUrl, run.channelId);
if (!isSynced) {
// We don't update the run because any information we currently have may be outdated
return;
}
await operator.handlePlaybookChecklist({
checklists: [{
...(data.update?.fields || {}),
items_order: data.update?.items_order,
items: undefined, // Remove the items from the update
id: data.update.id,
update_at: data.update.checklist_updated_at,
run_id: data.playbook_run_id,
}],
prepareRecordsOnly: false,
processChildren: false,
});
if (data.update.item_inserts) {
if (checklistUpdate.item_inserts) {
const promises = [];
for (const item of data.update.item_inserts) {
for (const item of checklistUpdate.item_inserts) {
promises.push(operator.handlePlaybookChecklistItem({
items: [{...item, checklist_id: data.update.id, update_at: data.update.checklist_updated_at}],
prepareRecordsOnly: false,
items: [{...item, checklist_id: checklistUpdate.id, update_at: checklistUpdate.checklist_updated_at}],
prepareRecordsOnly: true,
}));
}
await Promise.all(promises);
const checklistItemModels = await Promise.all(promises);
models.push(...checklistItemModels.flat());
}
if (checklistUpdate.item_updates) {
const promises = [];
for (const item of checklistUpdate.item_updates) {
promises.push(handlePlaybookChecklistItemUpdated(serverUrl, item, checklistUpdate.id));
}
const checklistItemModels = await Promise.all(promises);
models.push(...checklistItemModels.flat());
}
return models;
};
export const handlePlaybookChecklistItemUpdated = async (serverUrl: string, msg: WebSocketMessage) => {
if (!msg.data.payload) {
return;
}
const data = safeParseJSON(msg.data.payload) as PlaybookChecklistItemUpdatePayload;
if (!isValidEvent(data)) {
return;
}
const handlePlaybookChecklistItemUpdated = async (serverUrl: string, itemUpdate: PlaybookChecklistItemUpdate, checklistId: string) => {
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const run = await getPlaybookRunById(database, data.playbook_run_id);
if (!run) {
// Do not handle any data if the run is not in the database
return;
}
const isSynced = EphemeralStore.getChannelPlaybooksSynced(serverUrl, run.channelId);
if (!isSynced) {
// We don't update the run because any information we currently have may be outdated
return;
}
await operator.handlePlaybookChecklistItem({
items: [{...data.update.fields, id: data.update.id, checklist_id: data.checklist_id, update_at: data.update.checklist_item_updated_at}],
prepareRecordsOnly: false,
return operator.handlePlaybookChecklistItem({
items: [{...itemUpdate.fields, id: itemUpdate.id, checklist_id: checklistId, update_at: itemUpdate.checklist_item_updated_at}],
prepareRecordsOnly: true,
});
};

View file

@ -7,6 +7,4 @@ export const WEBSOCKET_EVENTS = {
WEBSOCKET_PLAYBOOK_RUN_UPDATED: `custom_${PLAYBOOKS_PLUGIN_ID}_playbook_run_updated`,
WEBSOCKET_PLAYBOOK_RUN_CREATED: `custom_${PLAYBOOKS_PLUGIN_ID}_playbook_run_created`,
WEBSOCKET_PLAYBOOK_RUN_UPDATED_INCREMENTAL: `custom_${PLAYBOOKS_PLUGIN_ID}_playbook_run_updated_incremental`,
WEBSOCKET_PLAYBOOK_CHECKLIST_UPDATED: `custom_${PLAYBOOKS_PLUGIN_ID}_playbook_checklist_updated`,
WEBSOCKET_PLAYBOOK_CHECKLIST_ITEM_UPDATED: `custom_${PLAYBOOKS_PLUGIN_ID}_playbook_checklist_item_updated`,
};

View file

@ -13,13 +13,8 @@ type PlaybookRunUpdate = {
};
}
type PlaybookChecklistUpdatePayload = {
playbook_run_id: string;
update: PlaybookChecklistUpdate;
}
type PlaybookChecklistUpdate = {
id: string;
index: number;
checklist_updated_at: number;
items_order?: string[];
fields?: Omit<Partial<PlaybookChecklist>, 'items'> & {
@ -30,15 +25,8 @@ type PlaybookChecklistUpdate = {
item_inserts?: PlaybookChecklistItem[];
}
type PlaybookChecklistItemUpdatePayload = {
playbook_run_id: string;
checklist_id: string;
update: PlaybookChecklistItemUpdate;
}
type PlaybookChecklistItemUpdate = {
id: string;
index: number;
checklist_item_updated_at: number;
fields: Partial<PlaybookChecklistItem>;
}