Gekidou - Sidebar websocket handling (#5954)
* Adds actions, queries, db models and rest client * Adds Websocket handling for categories * fix merge * small refactor for categories websocket events * fix bad merge * Category delete handled * Handles deletion (prune on store) * Resolves feedback, adds team handling for prune * Minor fixes; param order and return, imports, type naming Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
This commit is contained in:
parent
b256f7fcce
commit
826c6683f8
9 changed files with 177 additions and 8 deletions
|
|
@ -3,11 +3,36 @@
|
|||
|
||||
import {Model} from '@nozbe/watermelondb';
|
||||
|
||||
import {prepareCategories, prepareCategoryChannels} from '@app/queries/servers/categories';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {prepareCategories, prepareCategoryChannels, queryCategoriesByTeamIds, queryCategoryById} from '@queries/servers/categories';
|
||||
import {pluckUnique} from '@utils/helpers';
|
||||
|
||||
export const storeCategories = async (serverUrl: string, categories: CategoryWithChannels[], prepareRecordsOnly = false) => {
|
||||
export const deleteCategory = async (serverUrl: string, categoryId: string) => {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
if (!database) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
try {
|
||||
const category = await queryCategoryById(database, categoryId);
|
||||
|
||||
if (category) {
|
||||
await database.write(async () => {
|
||||
await category.destroyPermanently();
|
||||
});
|
||||
}
|
||||
|
||||
return {category};
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('FAILED TO DELETE CATEGORY', categoryId);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const storeCategories = async (serverUrl: string, categories: CategoryWithChannels[], prune = false, prepareRecordsOnly = false) => {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
|
@ -25,6 +50,22 @@ export const storeCategories = async (serverUrl: string, categories: CategoryWit
|
|||
const models = await Promise.all(modelPromises);
|
||||
const flattenedModels = models.flat() as Model[];
|
||||
|
||||
if (prune && categories.length) {
|
||||
const {database} = operator;
|
||||
const remoteCategoryIds = categories.map((cat) => cat.id);
|
||||
|
||||
// If the passed categories have more than one team, we want to update across teams
|
||||
const teamIds = pluckUnique('team_id')(categories) as string[];
|
||||
const localCategories = await queryCategoriesByTeamIds(database, teamIds);
|
||||
|
||||
localCategories.forEach((localCategory) => {
|
||||
if (!remoteCategoryIds.includes(localCategory.id)) {
|
||||
localCategory.prepareDestroyPermanently();
|
||||
flattenedModels.push(localCategory);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (prepareRecordsOnly) {
|
||||
return {models: flattenedModels};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export type CategoriesRequest = {
|
|||
error?: unknown;
|
||||
}
|
||||
|
||||
export const fetchCategories = async (serverUrl: string, teamId: string, fetchOnly = false): Promise<CategoriesRequest> => {
|
||||
export const fetchCategories = async (serverUrl: string, teamId: string, prune = false, fetchOnly = false): Promise<CategoriesRequest> => {
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
|
|
@ -25,7 +25,7 @@ export const fetchCategories = async (serverUrl: string, teamId: string, fetchOn
|
|||
const {categories} = await client.getCategories('me', teamId);
|
||||
|
||||
if (!fetchOnly) {
|
||||
storeCategories(serverUrl, categories);
|
||||
storeCategories(serverUrl, categories, prune);
|
||||
}
|
||||
|
||||
return {categories};
|
||||
|
|
|
|||
|
|
@ -220,7 +220,7 @@ export const fetchMyChannelsForTeam = async (serverUrl: string, teamId: string,
|
|||
|
||||
if (!fetchOnly) {
|
||||
storeMyChannelsForTeam(serverUrl, teamId, channels, memberships);
|
||||
storeCategories(serverUrl, categories);
|
||||
storeCategories(serverUrl, categories, true); // Re-sync
|
||||
}
|
||||
|
||||
return {channels, memberships, categories};
|
||||
|
|
|
|||
96
app/actions/websocket/category.ts
Normal file
96
app/actions/websocket/category.ts
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {deleteCategory, storeCategories} from '@actions/local/category';
|
||||
import {fetchCategories} from '@actions/remote/category';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {queryCategoriesById} from '@queries/servers/categories';
|
||||
|
||||
type WebsocketCategoriesMessage = {
|
||||
broadcast: {
|
||||
team_id: string;
|
||||
};
|
||||
data: {
|
||||
team_id: string;
|
||||
category?: string;
|
||||
category_id: string;
|
||||
updatedCategories?: string;
|
||||
order?: string[];
|
||||
};
|
||||
}
|
||||
|
||||
const addOrUpdateCategories = async (serverUrl: string, categories: CategoryWithChannels[]) => {
|
||||
try {
|
||||
storeCategories(serverUrl, categories);
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Category WS: addOrUpdateCategories', e, categories);
|
||||
}
|
||||
};
|
||||
|
||||
export async function handleCategoryCreatedEvent(serverUrl: string, msg: WebsocketCategoriesMessage) {
|
||||
let category;
|
||||
try {
|
||||
category = JSON.parse(msg.data.category!);
|
||||
addOrUpdateCategories(serverUrl, [category]);
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Category WS: handleCategoryCreatedEvent', e, msg);
|
||||
fetchCategories(serverUrl, msg.broadcast.team_id);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleCategoryUpdatedEvent(serverUrl: string, msg: WebsocketCategoriesMessage) {
|
||||
let categories;
|
||||
|
||||
try {
|
||||
categories = JSON.parse(msg.data.updatedCategories!);
|
||||
addOrUpdateCategories(serverUrl, categories);
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Category WS: handleCategoryUpdatedEvent', e, msg);
|
||||
fetchCategories(serverUrl, msg.broadcast.team_id, true);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleCategoryDeletedEvent(serverUrl: string, msg: WebsocketCategoriesMessage) {
|
||||
try {
|
||||
// Delete the Category
|
||||
await deleteCategory(serverUrl, msg.data.category_id);
|
||||
|
||||
// Fetch the categories again as channels will have moved
|
||||
fetchCategories(serverUrl, msg.broadcast.team_id);
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Category WS: handleCategoryDeletedEvent', e, msg);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleCategoryOrderUpdatedEvent(serverUrl: string, msg: WebsocketCategoriesMessage) {
|
||||
try {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
|
||||
if (!operator) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {database} = operator;
|
||||
|
||||
// Update category order
|
||||
if (msg.data.order?.length) {
|
||||
const order = msg.data.order;
|
||||
const categories = await queryCategoriesById(database, order);
|
||||
categories.forEach((c) => {
|
||||
const findOrder = (id: string) => id === c.id;
|
||||
c.prepareUpdate(() => {
|
||||
c.sortOrder = order.findIndex(findOrder);
|
||||
});
|
||||
});
|
||||
await operator.batchRecords(categories);
|
||||
}
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Category WS: handleCategoryOrderUpdatedEvent', e, msg);
|
||||
fetchCategories(serverUrl, msg.data.team_id);
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ import {queryCommonSystemValues, queryConfig, queryCurrentChannelId, queryWebSoc
|
|||
import {deleteMyTeams, queryTeamsById} from '@queries/servers/team';
|
||||
import {isTablet} from '@utils/helpers';
|
||||
|
||||
import {handleCategoryCreatedEvent, handleCategoryDeletedEvent, handleCategoryOrderUpdatedEvent, handleCategoryUpdatedEvent} from './category';
|
||||
import {handleChannelDeletedEvent, handleUserAddedToChannelEvent, handleUserRemovedFromChannelEvent} from './channel';
|
||||
import {handleNewPostEvent, handlePostDeleted, handlePostEdited, handlePostUnread} from './posts';
|
||||
import {handlePreferenceChangedEvent, handlePreferencesChangedEvent, handlePreferencesDeletedEvent} from './preferences';
|
||||
|
|
@ -243,6 +244,19 @@ export async function handleEvent(serverUrl: string, msg: WebSocketMessage) {
|
|||
handleTeamMemberRoleUpdatedEvent(serverUrl, msg);
|
||||
break;
|
||||
|
||||
case WebsocketEvents.CATEGORY_CREATED:
|
||||
handleCategoryCreatedEvent(serverUrl, msg);
|
||||
break;
|
||||
case WebsocketEvents.CATEGORY_UPDATED:
|
||||
handleCategoryUpdatedEvent(serverUrl, msg);
|
||||
break;
|
||||
case WebsocketEvents.CATEGORY_ORDER_UPDATED:
|
||||
handleCategoryOrderUpdatedEvent(serverUrl, msg);
|
||||
break;
|
||||
case WebsocketEvents.CATEGORY_DELETED:
|
||||
handleCategoryDeletedEvent(serverUrl, msg);
|
||||
break;
|
||||
|
||||
case WebsocketEvents.CHANNEL_CREATED:
|
||||
break;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {SYSTEM_IDENTIFIERS} from '@app/constants/database';
|
||||
import {SYSTEM_IDENTIFIERS} from '@constants/database';
|
||||
import DatabaseManager from '@database/manager';
|
||||
|
||||
export async function handleLicenseChangedEvent(serverUrl: string, msg: WebSocketMessage): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@ const WebsocketEvents = {
|
|||
POST_EDITED: 'post_edited',
|
||||
POST_DELETED: 'post_deleted',
|
||||
POST_UNREAD: 'post_unread',
|
||||
CATEGORY_CREATED: 'sidebar_category_created',
|
||||
CATEGORY_UPDATED: 'sidebar_category_updated',
|
||||
CATEGORY_DELETED: 'sidebar_category_deleted',
|
||||
CATEGORY_ORDER_UPDATED: 'sidebar_category_order_updated',
|
||||
CHANNEL_CONVERTED: 'channel_converted',
|
||||
CHANNEL_CREATED: 'channel_created',
|
||||
CHANNEL_DELETED: 'channel_deleted',
|
||||
|
|
|
|||
|
|
@ -46,6 +46,15 @@ export const queryCategoriesByTeamId = async (database: Database, teamId: string
|
|||
}
|
||||
};
|
||||
|
||||
export const queryCategoriesByTeamIds = async (database: Database, teamIds: string[]): Promise<CategoryModel[]> => {
|
||||
try {
|
||||
const records = (await database.get<CategoryModel>(CATEGORY).query(Q.where('team_id', Q.oneOf(teamIds))).fetch());
|
||||
return records;
|
||||
} catch {
|
||||
return Promise.resolve([] as CategoryModel[]);
|
||||
}
|
||||
};
|
||||
|
||||
export const queryCategoriesByTypeTeamId = async (database: Database, type: CategoryType, teamId: string): Promise<CategoryModel[]> => {
|
||||
try {
|
||||
const records = await database.get<CategoryModel>(CATEGORY).query(
|
||||
|
|
@ -94,9 +103,12 @@ export const prepareCategoryChannels = (
|
|||
});
|
||||
});
|
||||
|
||||
const categoryChannelRecords = operator.handleCategoryChannels({categoryChannels, prepareRecordsOnly: true});
|
||||
if (categoryChannels.length) {
|
||||
const categoryChannelRecords = operator.handleCategoryChannels({categoryChannels, prepareRecordsOnly: true});
|
||||
return [categoryChannelRecords];
|
||||
}
|
||||
|
||||
return [categoryChannelRecords];
|
||||
return [];
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,3 +137,5 @@ export async function isTablet() {
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
export const pluckUnique = (key: string) => (array: Array<{[key: string]: unknown}>) => Array.from(new Set(array.map((obj) => obj[key])));
|
||||
|
|
|
|||
Loading…
Reference in a new issue