Remove playbooks on delete channel (#8994)
Co-authored-by: Mattermost Build <build@mattermost.com>
This commit is contained in:
parent
b925baa21b
commit
2455ecf39f
20 changed files with 342 additions and 25 deletions
|
|
@ -116,7 +116,7 @@ export async function removeCurrentUserFromChannel(serverUrl: string, channelId:
|
|||
if (!channel) {
|
||||
throw new Error('myChannel present but no channel on the database');
|
||||
}
|
||||
models.push(...await prepareDeleteChannel(channel));
|
||||
models.push(...await prepareDeleteChannel(serverUrl, channel));
|
||||
let teamId = channel.teamId;
|
||||
if (teamId) {
|
||||
teamId = await getCurrentTeamId(database);
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ describe('removeUserFromTeam', () => {
|
|||
|
||||
expect(getMyTeamById).toHaveBeenCalledWith(database, team.id);
|
||||
expect(getTeamById).toHaveBeenCalledWith(database, myTeam.id);
|
||||
expect(prepareDeleteTeam).toHaveBeenCalledWith(team);
|
||||
expect(prepareDeleteTeam).toHaveBeenCalledWith(serverUrl, team);
|
||||
expect(removeTeamFromTeamHistory).toHaveBeenCalledWith(operator, team.id, true);
|
||||
expect(operator.batchRecords).toHaveBeenCalledWith(preparedModels, 'removeUserFromTeam');
|
||||
|
||||
|
|
@ -166,7 +166,7 @@ describe('removeUserFromTeam', () => {
|
|||
|
||||
expect(getMyTeamById).toHaveBeenCalledWith(database, teamId);
|
||||
expect(getTeamById).toHaveBeenCalledWith(database, myTeam.id);
|
||||
expect(prepareDeleteTeam).toHaveBeenCalledWith(team);
|
||||
expect(prepareDeleteTeam).toHaveBeenCalledWith(serverUrl, team);
|
||||
expect(removeTeamFromTeamHistory).toHaveBeenCalledWith(operator, team.id, true);
|
||||
expect(operator.batchRecords).toHaveBeenCalledWith(preparedModels, 'removeUserFromTeam');
|
||||
expect(logError).toHaveBeenCalledWith('Failed removeUserFromTeam', writeError);
|
||||
|
|
@ -189,7 +189,7 @@ describe('removeUserFromTeam', () => {
|
|||
|
||||
expect(getMyTeamById).toHaveBeenCalledWith(database, teamId);
|
||||
expect(getTeamById).not.toHaveBeenCalled();
|
||||
expect(prepareDeleteTeam).not.toHaveBeenCalled();
|
||||
expect(prepareDeleteTeam).not.toHaveBeenCalledWith(serverUrl, null as any);
|
||||
expect(removeTeamFromTeamHistory).not.toHaveBeenCalled();
|
||||
expect(operator.batchRecords).not.toHaveBeenCalled();
|
||||
expect(result).toEqual({error: undefined});
|
||||
|
|
@ -238,7 +238,7 @@ describe('removeUserFromTeam', () => {
|
|||
|
||||
expect(getMyTeamById).toHaveBeenCalledWith(database, teamId);
|
||||
expect(getTeamById).toHaveBeenCalledWith(database, myTeam.id);
|
||||
expect(prepareDeleteTeam).toHaveBeenCalledWith(team);
|
||||
expect(prepareDeleteTeam).toHaveBeenCalledWith(serverUrl, team);
|
||||
expect(removeTeamFromTeamHistory).toHaveBeenCalledWith(operator, team.id, true);
|
||||
expect(operator.batchRecords).toHaveBeenCalledWith(preparedModels, 'removeUserFromTeam');
|
||||
expect(result).toEqual({error: undefined});
|
||||
|
|
@ -258,7 +258,7 @@ describe('removeUserFromTeam', () => {
|
|||
|
||||
expect(getMyTeamById).toHaveBeenCalledWith(database, teamId);
|
||||
expect(getTeamById).toHaveBeenCalledWith(database, myTeam.id);
|
||||
expect(prepareDeleteTeam).toHaveBeenCalledWith(team);
|
||||
expect(prepareDeleteTeam).toHaveBeenCalledWith(serverUrl, team);
|
||||
expect(removeTeamFromTeamHistory).toHaveBeenCalledWith(operator, team.id, true);
|
||||
expect(operator.batchRecords).not.toHaveBeenCalled();
|
||||
expect(result).toEqual({error: undefined});
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export async function removeUserFromTeam(serverUrl: string, teamId: string) {
|
|||
if (!team) {
|
||||
throw new Error('Team not found');
|
||||
}
|
||||
const models = await prepareDeleteTeam(team);
|
||||
const models = await prepareDeleteTeam(serverUrl, team);
|
||||
const system = await removeTeamFromTeamHistory(operator, team.id, true);
|
||||
if (system) {
|
||||
models.push(...system);
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ const entryRest = async (serverUrl: string, teamId?: string, channelId?: string,
|
|||
initialChannelId = await entryInitialChannelId(database, initialChannelId, teamId, initialTeamId, meData?.user?.locale || '', chData?.channels, chData?.memberships);
|
||||
|
||||
const dt = Date.now();
|
||||
const modelsToDeletePromises = await prepareEntryModelsForDeletion({operator, teamData, chData});
|
||||
const modelsToDeletePromises = await prepareEntryModelsForDeletion({serverUrl, operator, teamData, chData});
|
||||
const modelPromises = await prepareEntryModels({operator, teamData, chData, prefData, meData, isCRTEnabled});
|
||||
const modelsToDelete = await Promise.all(modelsToDeletePromises);
|
||||
const models = await Promise.all(modelPromises);
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ export async function fetchMyTeams(serverUrl: string, fetchOnly = false, groupLa
|
|||
// Immediately delete myTeams so that the UI renders only teams the user is a member of.
|
||||
const removeTeams = await queryTeamsById(database, Array.from(removeTeamIds)).fetch();
|
||||
removeTeams.forEach((team) => {
|
||||
modelPromises.push(prepareDeleteTeam(team));
|
||||
modelPromises.push(prepareDeleteTeam(serverUrl, team));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,90 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {PLAYBOOK_TABLES} from '@playbooks/constants/database';
|
||||
import TestHelper from '@test/test_helper';
|
||||
|
||||
import type ServerDataOperator from '@database/operator/server_data_operator';
|
||||
import type PlaybookChecklistModel from '@playbooks/types/database/models/playbook_checklist';
|
||||
|
||||
const {PLAYBOOK_CHECKLIST, PLAYBOOK_CHECKLIST_ITEM} = PLAYBOOK_TABLES;
|
||||
|
||||
const SERVER_URL = 'playbookChecklistModel.test.com';
|
||||
|
||||
describe('PlaybookChecklistModel', () => {
|
||||
let operator: ServerDataOperator;
|
||||
|
||||
beforeEach(async () => {
|
||||
await DatabaseManager.init([SERVER_URL]);
|
||||
operator = DatabaseManager.serverDatabases[SERVER_URL]!.operator;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await DatabaseManager.destroyServerDatabase(SERVER_URL);
|
||||
});
|
||||
|
||||
describe('prepareDestroyWithRelations', () => {
|
||||
it('should prepare checklist and all its items for destruction when checklist has items', async () => {
|
||||
// Create a playbook run first
|
||||
const mockRuns = TestHelper.createPlaybookRuns(1, 1, 3, true);
|
||||
|
||||
await operator.handlePlaybookRun({
|
||||
runs: mockRuns,
|
||||
prepareRecordsOnly: false,
|
||||
processChildren: true,
|
||||
});
|
||||
|
||||
const checklistId = mockRuns[0].checklists[0].id;
|
||||
|
||||
// Get the created checklist model
|
||||
const checklistModel = await operator.database.get<PlaybookChecklistModel>(PLAYBOOK_CHECKLIST).find(checklistId);
|
||||
|
||||
// Call the method under test
|
||||
const preparedModels = await checklistModel.prepareDestroyWithRelations();
|
||||
|
||||
// Verify the result
|
||||
expect(preparedModels).toHaveLength(4); // 1 checklist + 3 items
|
||||
|
||||
// Verify the checklist is prepared for destruction
|
||||
const checklistPrepared = preparedModels.find((model) => model.id === checklistId);
|
||||
expect(checklistPrepared).toBeDefined();
|
||||
expect(checklistPrepared!.collection.table).toBe(PLAYBOOK_CHECKLIST);
|
||||
|
||||
// Verify all items are prepared for destruction
|
||||
const itemIds = mockRuns[0].checklists[0].items.map((item) => item.id);
|
||||
itemIds.forEach((itemId) => {
|
||||
const itemPrepared = preparedModels.find((model) => model.id === itemId);
|
||||
expect(itemPrepared).toBeDefined();
|
||||
expect(itemPrepared!.collection.table).toBe(PLAYBOOK_CHECKLIST_ITEM);
|
||||
});
|
||||
});
|
||||
|
||||
it('should prepare only checklist for destruction when checklist has no items', async () => {
|
||||
// Create a playbook run first
|
||||
const mockRuns = TestHelper.createPlaybookRuns(1, 1, 0, true);
|
||||
|
||||
await operator.handlePlaybookRun({
|
||||
runs: mockRuns,
|
||||
prepareRecordsOnly: false,
|
||||
processChildren: true,
|
||||
});
|
||||
|
||||
const checklistId = mockRuns[0].checklists[0].id;
|
||||
|
||||
// Get the created checklist model
|
||||
const checklistModel = await operator.database.get<PlaybookChecklistModel>(PLAYBOOK_CHECKLIST).find(checklistId);
|
||||
|
||||
// Call the method under test
|
||||
const preparedModels = await checklistModel.prepareDestroyWithRelations();
|
||||
|
||||
// Verify the result
|
||||
expect(preparedModels).toHaveLength(1); // Only the checklist
|
||||
|
||||
// Verify the checklist is prepared for destruction
|
||||
const checklistPrepared = preparedModels[0];
|
||||
expect(checklistPrepared.id).toBe(checklistId);
|
||||
expect(checklistPrepared.collection.table).toBe(PLAYBOOK_CHECKLIST);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -54,4 +54,18 @@ export default class PlaybookChecklistModel extends Model implements PlaybookChe
|
|||
|
||||
/** run : The playbook run to which this checklist belongs */
|
||||
@immutableRelation(PLAYBOOK_RUN, 'run_id') run!: Relation<PlaybookRunModel>;
|
||||
|
||||
prepareDestroyWithRelations = async (): Promise<Model[]> => {
|
||||
const preparedModels: Model[] = [this.prepareDestroyPermanently()];
|
||||
|
||||
const items = await this.items?.fetch();
|
||||
if (items?.length) {
|
||||
for await (const item of items) {
|
||||
const preparedItem = item.prepareDestroyPermanently();
|
||||
preparedModels.push(preparedItem);
|
||||
}
|
||||
}
|
||||
|
||||
return preparedModels;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
136
app/products/playbooks/database/models/playbook_run.test.ts
Normal file
136
app/products/playbooks/database/models/playbook_run.test.ts
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {PLAYBOOK_TABLES} from '@playbooks/constants/database';
|
||||
import TestHelper from '@test/test_helper';
|
||||
|
||||
import type ServerDataOperator from '@database/operator/server_data_operator';
|
||||
import type PlaybookRunModel from '@playbooks/types/database/models/playbook_run';
|
||||
|
||||
const {PLAYBOOK_RUN, PLAYBOOK_CHECKLIST, PLAYBOOK_CHECKLIST_ITEM} = PLAYBOOK_TABLES;
|
||||
|
||||
const SERVER_URL = 'playbookRunModel.test.com';
|
||||
|
||||
describe('PlaybookRunModel', () => {
|
||||
let operator: ServerDataOperator;
|
||||
|
||||
beforeEach(async () => {
|
||||
await DatabaseManager.init([SERVER_URL]);
|
||||
operator = DatabaseManager.serverDatabases[SERVER_URL]!.operator;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await DatabaseManager.destroyServerDatabase(SERVER_URL);
|
||||
});
|
||||
|
||||
describe('prepareDestroyWithRelations', () => {
|
||||
it('should prepare run and all its checklists and items for destruction when run has checklists with items', async () => {
|
||||
// Create playbook runs with checklists and items
|
||||
const mockRuns = TestHelper.createPlaybookRuns(1, 2, 3, true); // 1 run, 2 checklists, 3 items per checklist
|
||||
|
||||
await operator.handlePlaybookRun({
|
||||
runs: mockRuns,
|
||||
prepareRecordsOnly: false,
|
||||
processChildren: true,
|
||||
});
|
||||
|
||||
const runId = mockRuns[0].id;
|
||||
|
||||
// Get the created run model
|
||||
const runModel = await operator.database.get<PlaybookRunModel>(PLAYBOOK_RUN).find(runId);
|
||||
|
||||
// Call the method under test
|
||||
const preparedModels = await runModel.prepareDestroyWithRelations();
|
||||
|
||||
// Calculate expected count: 1 run + 2 checklists + (2 checklists * 3 items each) = 9 total
|
||||
const expectedCount = 1 + 2 + (2 * 3);
|
||||
expect(preparedModels).toHaveLength(expectedCount);
|
||||
|
||||
// Verify the run is prepared for destruction
|
||||
const runPrepared = preparedModels.find((model) => model.id === runId);
|
||||
expect(runPrepared).toBeDefined();
|
||||
expect(runPrepared!.collection.table).toBe(PLAYBOOK_RUN);
|
||||
|
||||
// Verify all checklists are prepared for destruction
|
||||
const checklistIds = mockRuns[0].checklists.map((checklist) => checklist.id);
|
||||
checklistIds.forEach((checklistId) => {
|
||||
const checklistPrepared = preparedModels.find((model) => model.id === checklistId);
|
||||
expect(checklistPrepared).toBeDefined();
|
||||
expect(checklistPrepared!.collection.table).toBe(PLAYBOOK_CHECKLIST);
|
||||
});
|
||||
|
||||
// Verify all items are prepared for destruction
|
||||
const itemIds = mockRuns[0].checklists.flatMap((checklist) =>
|
||||
checklist.items.map((item) => item.id),
|
||||
);
|
||||
itemIds.forEach((itemId) => {
|
||||
const itemPrepared = preparedModels.find((model) => model.id === itemId);
|
||||
expect(itemPrepared).toBeDefined();
|
||||
expect(itemPrepared!.collection.table).toBe(PLAYBOOK_CHECKLIST_ITEM);
|
||||
});
|
||||
});
|
||||
|
||||
it('should prepare only run for destruction when run has no checklists', async () => {
|
||||
// Create playbook runs with no checklists
|
||||
const mockRuns = TestHelper.createPlaybookRuns(1, 0, 0, true); // 1 run, 0 checklists, 0 items
|
||||
|
||||
await operator.handlePlaybookRun({
|
||||
runs: mockRuns,
|
||||
prepareRecordsOnly: false,
|
||||
processChildren: true,
|
||||
});
|
||||
|
||||
const runId = mockRuns[0].id;
|
||||
|
||||
// Get the created run model
|
||||
const runModel = await operator.database.get<PlaybookRunModel>(PLAYBOOK_RUN).find(runId);
|
||||
|
||||
// Call the method under test
|
||||
const preparedModels = await runModel.prepareDestroyWithRelations();
|
||||
|
||||
// Verify the result
|
||||
expect(preparedModels).toHaveLength(1); // Only the run
|
||||
|
||||
// Verify the run is prepared for destruction
|
||||
const runPrepared = preparedModels[0];
|
||||
expect(runPrepared.id).toBe(runId);
|
||||
expect(runPrepared.collection.table).toBe(PLAYBOOK_RUN);
|
||||
});
|
||||
|
||||
it('should prepare run and checklists for destruction when checklists have no items', async () => {
|
||||
// Create playbook runs with checklists but no items
|
||||
const mockRuns = TestHelper.createPlaybookRuns(1, 3, 0, true); // 1 run, 3 checklists, 0 items
|
||||
|
||||
await operator.handlePlaybookRun({
|
||||
runs: mockRuns,
|
||||
prepareRecordsOnly: false,
|
||||
processChildren: true,
|
||||
});
|
||||
|
||||
const runId = mockRuns[0].id;
|
||||
|
||||
// Get the created run model
|
||||
const runModel = await operator.database.get<PlaybookRunModel>(PLAYBOOK_RUN).find(runId);
|
||||
|
||||
// Call the method under test
|
||||
const preparedModels = await runModel.prepareDestroyWithRelations();
|
||||
|
||||
// Verify the result: 1 run + 3 checklists = 4 total
|
||||
expect(preparedModels).toHaveLength(4);
|
||||
|
||||
// Verify the run is prepared for destruction
|
||||
const runPrepared = preparedModels.find((model) => model.id === runId);
|
||||
expect(runPrepared).toBeDefined();
|
||||
expect(runPrepared!.collection.table).toBe(PLAYBOOK_RUN);
|
||||
|
||||
// Verify all checklists are prepared for destruction
|
||||
const checklistIds = mockRuns[0].checklists.map((checklist) => checklist.id);
|
||||
checklistIds.forEach((checklistId) => {
|
||||
const checklistPrepared = preparedModels.find((model) => model.id === checklistId);
|
||||
expect(checklistPrepared).toBeDefined();
|
||||
expect(checklistPrepared!.collection.table).toBe(PLAYBOOK_CHECKLIST);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -141,4 +141,18 @@ export default class PlaybookRunModel extends Model implements PlaybookRunModelI
|
|||
const filteredParticipantIds = this.participantIds.filter((id) => id !== this.ownerUserId);
|
||||
return this.database.get<UserModel>(USER).query(Q.where('id', Q.oneOf(filteredParticipantIds)));
|
||||
};
|
||||
|
||||
prepareDestroyWithRelations = async (): Promise<Model[]> => {
|
||||
const preparedModels: Model[] = [this.prepareDestroyPermanently()];
|
||||
|
||||
const checklists = await this.checklists?.fetch();
|
||||
if (checklists?.length) {
|
||||
for await (const checklist of checklists) {
|
||||
const preparedChecklist = await checklist.prepareDestroyWithRelations();
|
||||
preparedModels.push(...preparedChecklist);
|
||||
}
|
||||
}
|
||||
|
||||
return preparedModels;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,9 @@ declare class PlaybookChecklistModel extends Model {
|
|||
|
||||
/** items : All the items associated with this checklist */
|
||||
items: Query<PlaybookChecklistItemModel>;
|
||||
|
||||
/** prepareDestroyWithRelations : Prepare the model for deletion with its relations */
|
||||
prepareDestroyWithRelations: () => Promise<Model[]>;
|
||||
}
|
||||
|
||||
export default PlaybookChecklistModel;
|
||||
|
|
|
|||
|
|
@ -109,6 +109,9 @@ declare class PlaybookRunModel extends Model {
|
|||
|
||||
/** participants : The USERS that participate in this Playbook Run */
|
||||
participants: () => Query<UserModel>;
|
||||
|
||||
/** prepareDestroyWithRelations : Prepare the model for deletion with its relations */
|
||||
prepareDestroyWithRelations: () => Promise<Model[]>;
|
||||
}
|
||||
|
||||
export default PlaybookRunModel;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {of as of$} from 'rxjs';
|
|||
import {General, Permissions} from '@constants';
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import ServerDataOperator from '@database/operator/server_data_operator';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
import TestHelper from '@test/test_helper';
|
||||
import {hasPermission} from '@utils/role';
|
||||
|
||||
|
|
@ -326,6 +327,7 @@ describe('prepareMyChannelsForTeam', () => {
|
|||
|
||||
describe('prepareDeleteChannel', () => {
|
||||
let channel: ChannelModel;
|
||||
const serverUrl = 'prepareDeleteChannel.test.com';
|
||||
|
||||
beforeEach(() => {
|
||||
channel = TestHelper.fakeChannelModel({
|
||||
|
|
@ -334,6 +336,8 @@ describe('prepareDeleteChannel', () => {
|
|||
});
|
||||
|
||||
it('should prepare models for deletion', async () => {
|
||||
const unsetSpy = jest.spyOn(EphemeralStore, 'unsetChannelPlaybooksSynced');
|
||||
|
||||
const membershipModel = TestHelper.fakeMyChannelModel({prepareDestroyPermanently: jest.fn().mockReturnValue({id: 'membership'})});
|
||||
const infoModel = TestHelper.fakeChannelInfoModel({prepareDestroyPermanently: jest.fn().mockReturnValue({id: 'info'})});
|
||||
const categoryChannelModel = TestHelper.fakeCategoryChannelModel({prepareDestroyPermanently: jest.fn().mockReturnValue({id: 'category'})});
|
||||
|
|
@ -348,6 +352,16 @@ describe('prepareDeleteChannel', () => {
|
|||
const bookmarkModels = [
|
||||
TestHelper.fakeChannelBookmarkModel({id: 'bookmark1', prepareDestroyPermanently: jest.fn().mockReturnValue({id: 'bookmark'})}),
|
||||
];
|
||||
const playbookRunModels = [
|
||||
TestHelper.fakePlaybookRunModel({
|
||||
id: 'playbookRun',
|
||||
prepareDestroyWithRelations: jest.fn().mockResolvedValue([
|
||||
TestHelper.fakePlaybookRunModel({
|
||||
id: 'playbookRun',
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
];
|
||||
|
||||
jest.mocked(channel.membership.fetch).mockResolvedValue(membershipModel);
|
||||
jest.mocked(channel.info.fetch).mockResolvedValue(infoModel);
|
||||
|
|
@ -357,8 +371,9 @@ describe('prepareDeleteChannel', () => {
|
|||
jest.mocked(channel.postsInChannel.fetch).mockResolvedValue(postsInChannelModels);
|
||||
jest.mocked(channel.posts.fetch).mockResolvedValue(postModels);
|
||||
jest.mocked(channel.bookmarks.fetch).mockResolvedValue(bookmarkModels);
|
||||
jest.mocked(channel.playbookRuns.fetch).mockResolvedValue(playbookRunModels);
|
||||
|
||||
const result = await prepareDeleteChannel(channel);
|
||||
const result = await prepareDeleteChannel(serverUrl, channel);
|
||||
|
||||
expect(result).toEqual([
|
||||
{},
|
||||
|
|
@ -370,6 +385,7 @@ describe('prepareDeleteChannel', () => {
|
|||
{id: 'postsInChannel'},
|
||||
{id: 'post'},
|
||||
{id: 'bookmark'},
|
||||
expect.objectContaining({id: 'playbookRun'}),
|
||||
]);
|
||||
expect(channel.prepareDestroyPermanently).toHaveBeenCalled();
|
||||
expect(channel.membership.fetch).toHaveBeenCalled();
|
||||
|
|
@ -380,6 +396,11 @@ describe('prepareDeleteChannel', () => {
|
|||
expect(channel.postsInChannel.fetch).toHaveBeenCalled();
|
||||
expect(channel.posts.fetch).toHaveBeenCalled();
|
||||
expect(channel.bookmarks.fetch).toHaveBeenCalled();
|
||||
expect(channel.playbookRuns.fetch).toHaveBeenCalled();
|
||||
expect(playbookRunModels[0].prepareDestroyWithRelations).toHaveBeenCalled();
|
||||
|
||||
// Should have cleared the playbooks synced
|
||||
expect(unsetSpy).toHaveBeenCalledWith(serverUrl, channel.id);
|
||||
});
|
||||
|
||||
it('should handle errors gracefully', async () => {
|
||||
|
|
@ -387,7 +408,7 @@ describe('prepareDeleteChannel', () => {
|
|||
jest.mocked(channel.info.fetch).mockRejectedValue(new Error('Test error'));
|
||||
jest.mocked(channel.categoryChannel.fetch).mockRejectedValue(new Error('Test error'));
|
||||
|
||||
const result = await prepareDeleteChannel(channel);
|
||||
const result = await prepareDeleteChannel(serverUrl, channel);
|
||||
|
||||
expect(result).toEqual([{}]);
|
||||
expect(channel.prepareDestroyPermanently).toHaveBeenCalled();
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {map as map$, switchMap, distinctUntilChanged, combineLatestWith} from 'r
|
|||
import {General, Permissions} from '@constants';
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import {sanitizeLikeString} from '@helpers/database';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
import {isDefaultChannel} from '@utils/channel';
|
||||
import {hasPermission} from '@utils/role';
|
||||
import {isSystemAdmin} from '@utils/user';
|
||||
|
|
@ -151,7 +152,7 @@ export const prepareMyChannelsForTeam = async (operator: ServerDataOperator, tea
|
|||
return prepareChannels(operator, channels, channelInfos, channelMembers, memberships, isCRTEnabled);
|
||||
};
|
||||
|
||||
export const prepareDeleteChannel = async (channel: ChannelModel): Promise<Model[]> => {
|
||||
export const prepareDeleteChannel = async (serverUrl: string, channel: ChannelModel): Promise<Model[]> => {
|
||||
const preparedModels: Model[] = [channel.prepareDestroyPermanently()];
|
||||
|
||||
const relations: Array<Relation<Model|MyChannelModel> | undefined> = [channel.membership, channel.info, channel.categoryChannel];
|
||||
|
|
@ -198,6 +199,16 @@ export const prepareDeleteChannel = async (channel: ChannelModel): Promise<Model
|
|||
}
|
||||
}
|
||||
|
||||
const playbookRuns = await channel.playbookRuns?.fetch();
|
||||
if (playbookRuns?.length) {
|
||||
for await (const run of playbookRuns) {
|
||||
const preparedRun = await run.prepareDestroyWithRelations();
|
||||
preparedModels.push(...preparedRun);
|
||||
}
|
||||
}
|
||||
|
||||
EphemeralStore.unsetChannelPlaybooksSynced(serverUrl, channel.id);
|
||||
|
||||
return preparedModels;
|
||||
};
|
||||
|
||||
|
|
@ -706,7 +717,7 @@ export const queryChannelsForAutocomplete = (database: Database, matchTerm: stri
|
|||
Q.where('type', Q.oneOf([General.DM_CHANNEL, General.GM_CHANNEL])),
|
||||
Q.on(CHANNEL_MEMBERSHIP, Q.on(USER,
|
||||
Q.or(
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
|
||||
// @ts-ignore: condition type error
|
||||
Q.unsafeSqlExpr(`first_name || ' ' || last_name LIKE '${likeTerm}'`),
|
||||
Q.where('nickname', Q.like(likeTerm)),
|
||||
|
|
|
|||
|
|
@ -157,6 +157,7 @@ describe('Entry Queries', () => {
|
|||
};
|
||||
|
||||
const promises = await prepareEntryModelsForDeletion({
|
||||
serverUrl,
|
||||
operator,
|
||||
teamData,
|
||||
});
|
||||
|
|
@ -190,6 +191,7 @@ describe('Entry Queries', () => {
|
|||
};
|
||||
|
||||
const promises = await prepareEntryModelsForDeletion({
|
||||
serverUrl,
|
||||
operator,
|
||||
chData,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ type PrepareModelsArgs = {
|
|||
}
|
||||
|
||||
type PrepareModelsForDeletionArgs = {
|
||||
serverUrl: string;
|
||||
operator: ServerDataOperator;
|
||||
initialTeamId?: string;
|
||||
teamData?: MyTeamsRequest;
|
||||
|
|
@ -48,7 +49,7 @@ const {
|
|||
MY_CHANNEL,
|
||||
} = MM_TABLES.SERVER;
|
||||
|
||||
export async function prepareEntryModelsForDeletion({operator, teamData, chData}: PrepareModelsForDeletionArgs): Promise<Array<Promise<Model[]>>> {
|
||||
export async function prepareEntryModelsForDeletion({serverUrl, operator, teamData, chData}: PrepareModelsForDeletionArgs): Promise<Array<Promise<Model[]>>> {
|
||||
const modelPromises: Array<Promise<Model[]>> = [];
|
||||
const {database} = operator;
|
||||
let teamIdsToDelete: Set<string>|undefined;
|
||||
|
|
@ -61,7 +62,7 @@ export async function prepareEntryModelsForDeletion({operator, teamData, chData}
|
|||
const removeTeams = await queryTeamsById(database, Array.from(teamIdsToDelete)).fetch();
|
||||
|
||||
removeTeams.forEach((team) => {
|
||||
modelPromises.push(prepareDeleteTeam(team));
|
||||
modelPromises.push(prepareDeleteTeam(serverUrl, team));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -79,7 +80,7 @@ export async function prepareEntryModelsForDeletion({operator, teamData, chData}
|
|||
if (removeChannelIds?.length) {
|
||||
removeChannels = await queryChannelsById(database, removeChannelIds).fetch();
|
||||
removeChannels.forEach((c) => {
|
||||
modelPromises.push(prepareDeleteChannel(c));
|
||||
modelPromises.push(prepareDeleteChannel(serverUrl, c));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -921,7 +921,7 @@ describe('Team Queries', () => {
|
|||
});
|
||||
|
||||
const team = await getTeamById(database, teamId);
|
||||
const records = await prepareDeleteTeam(team!);
|
||||
const records = await prepareDeleteTeam(serverUrl, team!);
|
||||
expect(records.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
|
|
@ -932,7 +932,7 @@ describe('Team Queries', () => {
|
|||
});
|
||||
|
||||
const team = await getTeamById(database, teamId);
|
||||
const records = await prepareDeleteTeam(team!);
|
||||
const records = await prepareDeleteTeam(serverUrl, team!);
|
||||
expect(records.length).toBe(1); // Just the team record
|
||||
});
|
||||
|
||||
|
|
@ -957,12 +957,12 @@ describe('Team Queries', () => {
|
|||
});
|
||||
|
||||
const team = await getTeamById(database, teamId);
|
||||
const records = await prepareDeleteTeam(team!);
|
||||
const records = await prepareDeleteTeam(serverUrl, team!);
|
||||
expect(records.length).toBe(1); // Just the team record
|
||||
});
|
||||
|
||||
it('should return empty array on error', async () => {
|
||||
const records = await prepareDeleteTeam(null as any);
|
||||
const records = await prepareDeleteTeam(serverUrl, null as any);
|
||||
expect(records).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -234,7 +234,7 @@ export async function deleteMyTeams(operator: ServerDataOperator, myTeams: MyTea
|
|||
}
|
||||
}
|
||||
|
||||
export const prepareDeleteTeam = async (team: TeamModel): Promise<Model[]> => {
|
||||
export const prepareDeleteTeam = async (serverUrl: string, team: TeamModel): Promise<Model[]> => {
|
||||
try {
|
||||
const preparedModels: Model[] = [team.prepareDestroyPermanently()];
|
||||
|
||||
|
|
@ -279,7 +279,7 @@ export const prepareDeleteTeam = async (team: TeamModel): Promise<Model[]> => {
|
|||
if (channels.length) {
|
||||
for await (const channel of channels) {
|
||||
try {
|
||||
const preparedChannel = await prepareDeleteChannel(channel);
|
||||
const preparedChannel = await prepareDeleteChannel(serverUrl, channel);
|
||||
preparedModels.push(...preparedChannel);
|
||||
} catch {
|
||||
// Record not found, do nothing
|
||||
|
|
|
|||
|
|
@ -27,5 +27,21 @@ describe('EphemeralStore', () => {
|
|||
EphemeralStore.clearChannelPlaybooksSynced('server-url');
|
||||
expect(EphemeralStore.getChannelPlaybooksSynced('server-url', 'channel-id')).toBe(false);
|
||||
expect(EphemeralStore.getChannelPlaybooksSynced('server-url-2', 'channel-id')).toBe(true); // The other server is not cleared
|
||||
|
||||
// Expect false if unset
|
||||
EphemeralStore.setChannelPlaybooksSynced('server-url', 'channel-id');
|
||||
EphemeralStore.setChannelPlaybooksSynced('server-url', 'channel-id-2');
|
||||
EphemeralStore.setChannelPlaybooksSynced('server-url', 'channel-id-3');
|
||||
|
||||
EphemeralStore.unsetChannelPlaybooksSynced('server-url', 'channel-id');
|
||||
expect(EphemeralStore.getChannelPlaybooksSynced('server-url', 'channel-id')).toBe(false);
|
||||
expect(EphemeralStore.getChannelPlaybooksSynced('server-url', 'channel-id-2')).toBe(true);
|
||||
expect(EphemeralStore.getChannelPlaybooksSynced('server-url', 'channel-id-3')).toBe(true);
|
||||
|
||||
// Expect false if cleared
|
||||
EphemeralStore.clearChannelPlaybooksSynced('server-url');
|
||||
expect(EphemeralStore.getChannelPlaybooksSynced('server-url', 'channel-id')).toBe(false);
|
||||
expect(EphemeralStore.getChannelPlaybooksSynced('server-url', 'channel-id-2')).toBe(false);
|
||||
expect(EphemeralStore.getChannelPlaybooksSynced('server-url', 'channel-id-3')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -296,6 +296,10 @@ class EphemeralStoreSingleton {
|
|||
this.channelPlaybooksSynced[serverUrl]?.add(channelId);
|
||||
};
|
||||
|
||||
unsetChannelPlaybooksSynced = (serverUrl: string, channelId: string) => {
|
||||
this.channelPlaybooksSynced[serverUrl]?.delete(channelId);
|
||||
};
|
||||
|
||||
clearChannelPlaybooksSynced = (serverUrl: string) => {
|
||||
delete this.channelPlaybooksSynced[serverUrl];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1099,13 +1099,13 @@ class TestHelperSingleton {
|
|||
};
|
||||
};
|
||||
|
||||
createPlaybookRuns = (runsCount = 1, maxChecklistCount = 1, maxItemsPerChecklist = 1): PlaybookRun[] => {
|
||||
createPlaybookRuns = (runsCount = 1, maxChecklistCount = 1, maxItemsPerChecklist = 1, omitRandom = false): PlaybookRun[] => {
|
||||
const playbookRuns: PlaybookRun[] = [];
|
||||
for (let i = 0; i < runsCount; i++) {
|
||||
const checklists: PlaybookChecklist[] = [];
|
||||
const checklistCount = Math.floor(Math.random() * maxChecklistCount) + 1;
|
||||
const checklistCount = omitRandom ? maxChecklistCount : Math.floor(Math.random() * maxChecklistCount) + 1;
|
||||
for (let j = 0; j < checklistCount; j++) {
|
||||
const itemsCount = Math.floor(Math.random() * maxItemsPerChecklist) + 1;
|
||||
const itemsCount = omitRandom ? maxItemsPerChecklist : Math.floor(Math.random() * maxItemsPerChecklist) + 1;
|
||||
checklists.push(this.createPlaybookChecklist(`playbook_run_${i}`, itemsCount, j));
|
||||
}
|
||||
playbookRuns.push({
|
||||
|
|
@ -1212,6 +1212,7 @@ class TestHelperSingleton {
|
|||
owner: this.fakeRelation(),
|
||||
checklists: this.fakeQuery([]),
|
||||
participants: () => this.fakeQuery([]),
|
||||
prepareDestroyWithRelations: jest.fn().mockResolvedValue([]),
|
||||
previousReminder: 0,
|
||||
itemsOrder: [],
|
||||
updateAt: 0,
|
||||
|
|
@ -1231,6 +1232,7 @@ class TestHelperSingleton {
|
|||
run: this.fakeRelation(),
|
||||
itemsOrder: [],
|
||||
updateAt: 0,
|
||||
prepareDestroyWithRelations: jest.fn().mockResolvedValue([]),
|
||||
...overwrite,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue