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:
parent
7086158e76
commit
2be07ec6d0
5 changed files with 213 additions and 397 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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`,
|
||||
};
|
||||
|
|
|
|||
12
app/products/playbooks/types/websocket.d.ts
vendored
12
app/products/playbooks/types/websocket.d.ts
vendored
|
|
@ -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>;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue