[Gekidou] Saves groups + group-channel for constrained channels (#6358)

* Rebases and addresses PR feedback

* Rebased and addresses PR feedback

* Checks group constraint in action instead

* Update method docs, parallel promises

* Parallel promises, method docs

* Cleans up method docs

* Method docs cleanup

* Update app/database/operator/server_data_operator/handlers/group.ts

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
This commit is contained in:
Shaz MJ
2022-08-01 08:02:15 +10:00
committed by GitHub
parent f94dfc9adf
commit 4b698c7f41
7 changed files with 132 additions and 16 deletions

View File

@@ -27,6 +27,7 @@ import {showMuteChannelSnackbar} from '@utils/snack_bar';
import {PERMALINK_GENERIC_TEAM_NAME_REDIRECT} from '@utils/url';
import {displayGroupMessageName, displayUsername} from '@utils/user';
import {fetchGroupsForChannelIfConstrained} from './groups';
import {fetchPostsForChannel} from './post';
import {setDirectChannelVisible} from './preference';
import {fetchRolesIfNeeded} from './role';
@@ -1109,6 +1110,7 @@ export async function switchToChannelById(serverUrl: string, channelId: string,
setDirectChannelVisible(serverUrl, channelId);
markChannelAsRead(serverUrl, channelId);
fetchChannelStats(serverUrl, channelId);
fetchGroupsForChannelIfConstrained(serverUrl, channelId);
DeviceEventEmitter.emit(Events.CHANNEL_SWITCH, false);

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {getChannelById} from '@app/queries/servers/channel';
import {Client} from '@client/rest';
import DatabaseManager from '@database/manager';
import NetworkManager from '@managers/network_manager';
@@ -48,7 +49,16 @@ export const fetchGroupsForChannel = async (serverUrl: string, channelId: string
const client = NetworkManager.getClient(serverUrl);
const response = await client.getAllGroupsAssociatedToChannel(channelId);
return operator.handleGroups({groups: response.groups, prepareRecordsOnly: fetchOnly});
const [groups, groupChannels] = await Promise.all([
operator.handleGroups({groups: response.groups, prepareRecordsOnly: true}),
operator.handleGroupChannelsForChannel({groups: response.groups, channelId, prepareRecordsOnly: true}),
]);
if (!fetchOnly) {
await operator.batchRecords([...groups, ...groupChannels]);
}
return {groups, groupChannels};
} catch (error) {
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
return {error};
@@ -141,3 +151,18 @@ export const fetchGroupsForTeamIfConstrained = async (serverUrl: string, teamId:
return {error};
}
};
export const fetchGroupsForChannelIfConstrained = async (serverUrl: string, channelId: string, fetchOnly = false) => {
try {
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const channel = await getChannelById(database, channelId);
if (channel?.isGroupConstrained) {
return fetchGroupsForChannel(serverUrl, channelId, fetchOnly);
}
return {};
} catch (error) {
return {error};
}
};

View File

@@ -2,21 +2,23 @@
// See LICENSE.txt for license information.
import {MM_TABLES} from '@constants/database';
import {transformGroupMembershipRecord, transformGroupRecord, transformGroupTeamRecord} from '@database/operator/server_data_operator/transformers/group';
import {transformGroupChannelRecord, transformGroupMembershipRecord, transformGroupRecord, transformGroupTeamRecord} from '@database/operator/server_data_operator/transformers/group';
import {getUniqueRawsBy} from '@database/operator/utils/general';
import {queryGroupMembershipForMember, queryGroupTeamForTeam} from '@queries/servers/group';
import {queryGroupChannelForChannel, queryGroupMembershipForMember, queryGroupTeamForTeam} from '@queries/servers/group';
import {generateGroupAssociationId} from '@utils/groups';
import {logWarning} from '@utils/log';
import type {HandleGroupArgs, HandleGroupMembershipForMemberArgs, HandleGroupTeamsForTeamArgs} from '@typings/database/database';
import type {HandleGroupArgs, HandleGroupChannelsForChannelArgs, HandleGroupMembershipForMemberArgs, HandleGroupTeamsForTeamArgs} from '@typings/database/database';
import type GroupModel from '@typings/database/models/servers/group';
import type GroupChannelModel from '@typings/database/models/servers/group_channel';
import type GroupMembershipModel from '@typings/database/models/servers/group_membership';
import type GroupTeamModel from '@typings/database/models/servers/group_team';
const {GROUP, GROUP_MEMBERSHIP, GROUP_TEAM} = MM_TABLES.SERVER;
const {GROUP, GROUP_CHANNEL, GROUP_MEMBERSHIP, GROUP_TEAM} = MM_TABLES.SERVER;
export interface GroupHandlerMix {
handleGroups: ({groups, prepareRecordsOnly}: HandleGroupArgs) => Promise<GroupModel[]>;
handleGroupChannelsForChannel: ({channelId, groups, prepareRecordsOnly}: HandleGroupChannelsForChannelArgs) => Promise<GroupChannelModel[]>;
handleGroupMembershipsForMember: ({userId, groups, prepareRecordsOnly}: HandleGroupMembershipForMemberArgs) => Promise<GroupMembershipModel[]>;
handleGroupTeamsForTeam: ({teamId, groups, prepareRecordsOnly}: HandleGroupTeamsForTeamArgs) => Promise<GroupTeamModel[]>;
}
@@ -48,6 +50,63 @@ const GroupHandler = (superclass: any) => class extends superclass implements Gr
});
};
/**
* handleGroupChannelsForChannel: Handler responsible for the Create/Update operations occurring on the GroupChannel table from the 'Server' schema
*
* @param {HandleGroupChannelsForChannelArgs}
* @returns {Promise<GroupChannelModel[]>}
*/
handleGroupChannelsForChannel = async ({channelId, groups, prepareRecordsOnly = true}: HandleGroupChannelsForChannelArgs): Promise<GroupChannelModel[]> => {
// Get existing group channels
const existingGroupChannels = await queryGroupChannelForChannel(this.database, channelId).fetch();
let records: GroupChannelModel[] = [];
let rawValues: GroupChannel[] = [];
// Nothing to add or remove
if (!groups?.length && !existingGroupChannels.length) {
return records;
} else if (!groups?.length && existingGroupChannels.length) { // No groups - remove all existing ones
records = existingGroupChannels.map((gt) => gt.prepareDestroyPermanently());
} else if (groups?.length && !existingGroupChannels.length) { // No existing groups - add all new ones
rawValues = groups.map((g) => ({id: generateGroupAssociationId(g.id, channelId), channel_id: channelId, group_id: g.id}));
} else if (groups?.length && existingGroupChannels.length) { // If both, we only want to save new ones and delete one's no longer in groups
const groupsSet: {[key: string]: GroupChannel} = {};
for (const g of groups) {
groupsSet[g.id] = {id: generateGroupAssociationId(g.id, channelId), channel_id: channelId, group_id: g.id};
}
for (const gt of existingGroupChannels) {
// Check if existingGroups overlaps with groups
if (groupsSet[gt.groupId]) {
// If there is an existing group already, we don't need to add it
delete groupsSet[gt.groupId];
} else {
// No group? Remove existing one
records.push(gt.prepareDestroyPermanently());
}
}
rawValues.push(...Object.values(groupsSet));
}
records.push(...(await this.handleRecords({
fieldName: 'id',
transformer: transformGroupChannelRecord,
rawValues,
tableName: GROUP_CHANNEL,
prepareRecordsOnly: true,
})));
// Batch update if there are records
if (records.length && !prepareRecordsOnly) {
await this.batchRecords(records);
}
return records;
};
/**
* handleGroupMembershipsForMember: Handler responsible for the Create/Update operations occurring on the GroupMembership table from the 'Server' schema
*

View File

@@ -9,11 +9,13 @@ import {generateGroupAssociationId} from '@utils/groups';
import type {TransformerArgs} from '@typings/database/database';
import type GroupModel from '@typings/database/models/servers/group';
import type GroupChannelModel from '@typings/database/models/servers/group_channel';
import type GroupMembershipModel from '@typings/database/models/servers/group_membership';
import type GroupTeamModel from '@typings/database/models/servers/group_team';
const {
GROUP,
GROUP_CHANNEL,
GROUP_MEMBERSHIP,
GROUP_TEAM,
} = MM_TABLES.SERVER;
@@ -49,6 +51,32 @@ export const transformGroupRecord = ({action, database, value}: TransformerArgs)
}) as Promise<GroupModel>;
};
/**
* transformGroupChannelRecord: Prepares a record of the SERVER database 'GroupChannel' table for update or create actions.
* @param {TransformerArgs} operator
* @param {Database} operator.database
* @param {RecordPair} operator.value
* @returns {Promise<GroupChannelModel>}
*/
export const transformGroupChannelRecord = ({action, database, value}: TransformerArgs): Promise<GroupChannelModel> => {
const raw = value.raw as GroupChannel;
// id of group comes from server response
const fieldsMapper = (model: GroupChannelModel) => {
model._raw.id = raw.id || generateGroupAssociationId(raw.group_id, raw.channel_id);
model.groupId = raw.group_id;
model.channelId = raw.channel_id;
};
return prepareBaseRecord({
action,
database,
tableName: GROUP_CHANNEL,
value,
fieldsMapper,
}) as Promise<GroupChannelModel>;
};
/**
* transformGroupMembershipRecord: Prepares a record of the SERVER database 'GroupMembership' table for update or create actions.
* @param {TransformerArgs} operator

View File

@@ -6,6 +6,7 @@ import {Database, Q} from '@nozbe/watermelondb';
import {MM_TABLES} from '@constants/database';
import type GroupModel from '@typings/database/models/servers/group';
import type GroupChannelModel from '@typings/database/models/servers/group_channel';
import type GroupMembershipModel from '@typings/database/models/servers/group_membership';
import type GroupTeamModel from '@typings/database/models/servers/group_team';
@@ -37,6 +38,12 @@ export const queryGroupsByNameInChannel = (database: Database, name: string, cha
);
};
export const queryGroupChannelForChannel = (database: Database, channelId: string) => {
return database.collections.get<GroupChannelModel>(GROUP_CHANNEL).query(
Q.where('channel_id', channelId),
);
};
export const queryGroupMembershipForMember = (database: Database, userId: string) => {
return database.collections.get<GroupMembershipModel>(GROUP_MEMBERSHIP).query(
Q.where('user_id', userId),

12
types/api/groups.d.ts vendored
View File

@@ -22,19 +22,9 @@ type GroupTeam = {
}
type GroupChannel = {
id?: string;
channel_id: string;
channel_display_name: string;
channel_type: string;
team_id: string;
team_display_name: string;
team_type: string;
group_id: string;
auto_add: boolean;
member_count?: number;
timezone_count?: number;
create_at: number;
delete_at: number;
update_at: number;
}
type GroupMembership = {

View File

@@ -222,6 +222,11 @@ export type HandleGroupArgs = PrepareOnly & {
groups?: Group[];
};
export type HandleGroupChannelsForChannelArgs = PrepareOnly & {
channelId: string;
groups?: Group[];
}
export type HandleGroupMembershipForMemberArgs = PrepareOnly & {
userId: string;
groups?: Group[];