Goodbye, GraphQL (#7597)

This commit is contained in:
Daniel Espino García 2023-10-16 10:13:16 +02:00 committed by GitHub
parent 1fb01dfcdc
commit a3eeb69277
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 41 additions and 1144 deletions

View file

@ -6,10 +6,10 @@ import {fetchGroupsForMember} from '@actions/remote/groups';
import {fetchPostsForUnreadChannels} from '@actions/remote/post';
import {type MyPreferencesRequest, fetchMyPreferences} from '@actions/remote/preference';
import {fetchRoles} from '@actions/remote/role';
import {fetchConfigAndLicense} from '@actions/remote/systems';
import {fetchConfigAndLicense, fetchDataRetentionPolicy} from '@actions/remote/systems';
import {fetchMyTeams, fetchTeamsChannelsAndUnreadPosts, handleKickFromTeam, type MyTeamsRequest, updateCanJoinTeams} from '@actions/remote/team';
import {syncTeamThreads} from '@actions/remote/thread';
import {fetchMe, type MyUserRequest, updateAllUsersSince} from '@actions/remote/user';
import {fetchMe, type MyUserRequest, updateAllUsersSince, autoUpdateTimezone} from '@actions/remote/user';
import {General, Preferences, Screens} from '@constants';
import {SYSTEM_IDENTIFIERS} from '@constants/database';
import {PUSH_PROXY_RESPONSE_NOT_AVAILABLE, PUSH_PROXY_RESPONSE_UNKNOWN, PUSH_PROXY_STATUS_NOT_AVAILABLE, PUSH_PROXY_STATUS_UNKNOWN, PUSH_PROXY_STATUS_VERIFIED} from '@constants/push_proxy';
@ -22,7 +22,7 @@ import {getDeviceToken} from '@queries/app/global';
import {queryAllChannelsForTeam, queryChannelsById} from '@queries/servers/channel';
import {prepareModels, truncateCrtRelatedTables} from '@queries/servers/entry';
import {getHasCRTChanged} from '@queries/servers/preference';
import {getConfig, getCurrentChannelId, getCurrentTeamId, getPushVerificationStatus, getWebSocketLastDisconnected, setCurrentTeamAndChannelId} from '@queries/servers/system';
import {getConfig, getCurrentChannelId, getCurrentTeamId, getIsDataRetentionEnabled, getPushVerificationStatus, getWebSocketLastDisconnected, setCurrentTeamAndChannelId} from '@queries/servers/system';
import {deleteMyTeams, getAvailableTeamIds, getTeamChannelHistory, queryMyTeams, queryMyTeamsByIds, queryTeamsById} from '@queries/servers/team';
import NavigationStore from '@store/navigation_store';
import {isDMorGM, sortChannelsByDisplayName} from '@utils/channel';
@ -63,13 +63,37 @@ export type EntryResponse = {
const FETCH_MISSING_DM_TIMEOUT = 2500;
export const FETCH_UNREADS_TIMEOUT = 2500;
export const getRemoveTeamIds = async (database: Database, teamData: MyTeamsRequest) => {
export const entry = async (serverUrl: string, teamId?: string, channelId?: string, since = 0): Promise<EntryResponse> => {
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const result = entryRest(serverUrl, teamId, channelId, since);
// Fetch data retention policies
const isDataRetentionEnabled = await getIsDataRetentionEnabled(database);
if (isDataRetentionEnabled) {
fetchDataRetentionPolicy(serverUrl);
}
return result;
};
export async function deferredAppEntryActions(
serverUrl: string, since: number, currentUserId: string, currentUserLocale: string, preferences: PreferenceType[] | undefined,
config: ClientConfig, license: ClientLicense | undefined, teamData: MyTeamsRequest, chData: MyChannelsRequest | undefined,
initialTeamId?: string, initialChannelId?: string) {
const result = restDeferredAppEntryActions(serverUrl, since, currentUserId, currentUserLocale, preferences, config, license, teamData, chData, initialTeamId, initialChannelId);
autoUpdateTimezone(serverUrl);
return result;
}
const getRemoveTeamIds = async (database: Database, teamData: MyTeamsRequest) => {
const myTeams = await queryMyTeams(database).fetch();
const joinedTeams = new Set(teamData.memberships?.filter((m) => m.delete_at === 0).map((m) => m.team_id));
return myTeams.filter((m) => !joinedTeams.has(m.id)).map((m) => m.id);
};
export const teamsToRemove = async (serverUrl: string, removeTeamIds?: string[]) => {
const teamsToRemove = async (serverUrl: string, removeTeamIds?: string[]) => {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return [];
@ -90,7 +114,7 @@ export const teamsToRemove = async (serverUrl: string, removeTeamIds?: string[])
return [];
};
export const entryRest = async (serverUrl: string, teamId?: string, channelId?: string, since = 0): Promise<EntryResponse> => {
const entryRest = async (serverUrl: string, teamId?: string, channelId?: string, since = 0): Promise<EntryResponse> => {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
@ -137,7 +161,7 @@ export const entryRest = async (serverUrl: string, teamId?: string, channelId?:
return {models: models.flat(), initialChannelId, initialTeamId, prefData, teamData, chData, meData};
};
export const fetchAppEntryData = async (serverUrl: string, sinceArg: number, initialTeamId = ''): Promise<AppEntryData | AppEntryError> => {
const fetchAppEntryData = async (serverUrl: string, sinceArg: number, initialTeamId = ''): Promise<AppEntryData | AppEntryError> => {
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
if (!database) {
return {error: `${serverUrl} database not found`};
@ -244,7 +268,7 @@ export const fetchAppEntryData = async (serverUrl: string, sinceArg: number, ini
return data;
};
export const fetchAlternateTeamData = async (
const fetchAlternateTeamData = async (
serverUrl: string, availableTeamIds: string[], removeTeamIds: string[],
includeDeleted = true, since = 0, fetchOnly = false, isCRTEnabled?: boolean) => {
let initialTeamId = '';
@ -269,7 +293,7 @@ export const fetchAlternateTeamData = async (
return {initialTeamId, removeTeamIds};
};
export async function entryInitialChannelId(database: Database, requestedChannelId = '', requestedTeamId = '', initialTeamId: string, locale: string, channels?: Channel[], memberships?: ChannelMember[]) {
async function entryInitialChannelId(database: Database, requestedChannelId = '', requestedTeamId = '', initialTeamId: string, locale: string, channels?: Channel[], memberships?: ChannelMember[]) {
const membershipIds = new Set(memberships?.map((m) => m.channel_id));
const requestedChannel = channels?.find((c) => (c.id === requestedChannelId) && membershipIds.has(c.id));
@ -307,7 +331,7 @@ export async function entryInitialChannelId(database: Database, requestedChannel
return myFirstTeamChannel?.id || '';
}
export async function restDeferredAppEntryActions(
async function restDeferredAppEntryActions(
serverUrl: string, since: number, currentUserId: string, currentUserLocale: string, preferences: PreferenceType[] | undefined,
config: ClientConfig, license: ClientLicense | undefined, teamData: MyTeamsRequest, chData: MyChannelsRequest | undefined,
initialTeamId?: string, initialChannelId?: string) {

View file

@ -1,310 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {storeConfigAndLicense} from '@actions/local/systems';
import {fetchGroupsForMember} from '@actions/remote/groups';
import {fetchPostsForUnreadChannels} from '@actions/remote/post';
import {fetchDataRetentionPolicy} from '@actions/remote/systems';
import {type MyTeamsRequest, updateCanJoinTeams} from '@actions/remote/team';
import {syncTeamThreads} from '@actions/remote/thread';
import {autoUpdateTimezone, fetchProfilesInGroupChannels, updateAllUsersSince} from '@actions/remote/user';
import {gqlEntry, gqlEntryChannels, gqlOtherChannels} from '@client/graphQL/entry';
import {General, Preferences} from '@constants';
import DatabaseManager from '@database/manager';
import {getPreferenceValue} from '@helpers/api/preference';
import {selectDefaultTeam} from '@helpers/api/team';
import {queryAllChannels, queryAllChannelsForTeam} from '@queries/servers/channel';
import {prepareModels, truncateCrtRelatedTables} from '@queries/servers/entry';
import {getHasCRTChanged} from '@queries/servers/preference';
import {getConfig, getIsDataRetentionEnabled} from '@queries/servers/system';
import {getFullErrorMessage} from '@utils/errors';
import {filterAndTransformRoles, getMemberChannelsFromGQLQuery, getMemberTeamsFromGQLQuery, gqlToClientChannelMembership, gqlToClientPreference, gqlToClientSidebarCategory, gqlToClientTeamMembership, gqlToClientUser} from '@utils/graphql';
import {logDebug} from '@utils/log';
import {processIsCRTEnabled} from '@utils/thread';
import {teamsToRemove, FETCH_UNREADS_TIMEOUT, entryRest, type EntryResponse, entryInitialChannelId, restDeferredAppEntryActions, getRemoveTeamIds} from './common';
import type {MyChannelsRequest} from '@actions/remote/channel';
import type {Database} from '@nozbe/watermelondb';
import type ChannelModel from '@typings/database/models/servers/channel';
const FETCH_MISSING_GM_TIMEOUT = 2500;
export async function deferredAppEntryGraphQLActions(
serverUrl: string,
since: number,
currentUserId: string,
teamData: MyTeamsRequest,
chData: MyChannelsRequest | undefined,
preferences: PreferenceType[] | undefined,
config: ClientConfig,
initialTeamId?: string,
initialChannelId?: string,
) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
const {database} = operator;
setTimeout(() => {
if (chData?.channels?.length && chData.memberships?.length) {
// defer fetching posts for unread channels on initial team
fetchPostsForUnreadChannels(serverUrl, chData.channels, chData.memberships, initialChannelId);
}
}, FETCH_UNREADS_TIMEOUT);
if (preferences && processIsCRTEnabled(preferences, config.CollapsedThreads, config.FeatureFlagCollapsedThreads, config.Version)) {
if (initialTeamId) {
await syncTeamThreads(serverUrl, initialTeamId);
}
if (teamData.teams?.length) {
for await (const team of teamData.teams) {
if (team.id !== initialTeamId) {
// need to await here since GM/DM threads in different teams overlap
await syncTeamThreads(serverUrl, team.id);
}
}
}
}
if (initialTeamId) {
const result = await getChannelData(serverUrl, initialTeamId, currentUserId, true);
if ('error' in result) {
return result;
}
const removeChannels = await getRemoveChannels(database, result.chData, initialTeamId, false);
const modelPromises = await prepareModels({operator, removeChannels, chData: result.chData}, true);
const roles = filterAndTransformRoles(result.roles);
if (roles.length) {
modelPromises.push(operator.handleRole({roles, prepareRecordsOnly: true}));
}
const models = (await Promise.all(modelPromises)).flat();
operator.batchRecords(models, 'deferredAppEntryActions');
setTimeout(() => {
if (result.chData?.channels?.length && result.chData.memberships?.length) {
// defer fetching posts for unread channels on other teams
fetchPostsForUnreadChannels(serverUrl, result.chData.channels, result.chData.memberships, initialChannelId);
}
}, FETCH_UNREADS_TIMEOUT);
}
// Fetch groups for current user
fetchGroupsForMember(serverUrl, currentUserId);
updateCanJoinTeams(serverUrl);
updateAllUsersSince(serverUrl, since);
// defer sidebar GM profiles
setTimeout(async () => {
const gmIds = chData?.channels?.reduce<Set<string>>((acc, v) => {
if (v?.type === General.GM_CHANNEL) {
acc.add(v.id);
}
return acc;
}, new Set<string>());
if (gmIds?.size) {
fetchProfilesInGroupChannels(serverUrl, Array.from(gmIds));
}
}, FETCH_MISSING_GM_TIMEOUT);
return {error: undefined};
}
const getRemoveChannels = async (database: Database, chData: MyChannelsRequest | undefined, initialTeamId: string, singleTeam: boolean) => {
const removeChannels: ChannelModel[] = [];
if (chData?.channels) {
const fetchedChannelIds = chData.channels?.map((channel) => channel.id);
const query = singleTeam ? queryAllChannelsForTeam(database, initialTeamId) : queryAllChannels(database);
const channels = await query.fetch();
for (const channel of channels) {
const excludeCondition = singleTeam ? true : channel.teamId !== initialTeamId && channel.teamId !== '';
if (excludeCondition && !fetchedChannelIds?.includes(channel.id)) {
removeChannels.push(channel);
}
}
}
return removeChannels;
};
const getChannelData = async (serverUrl: string, initialTeamId: string, userId: string, exclude: boolean): Promise<{chData: MyChannelsRequest; roles: Array<Partial<GQLRole>|undefined>} | {error: unknown}> => {
let response;
try {
const request = exclude ? gqlOtherChannels : gqlEntryChannels;
response = await request(serverUrl, initialTeamId);
} catch (error) {
return {error: getFullErrorMessage(error)};
}
if ('error' in response) {
return {error: response.error};
}
if ('errors' in response && response.errors?.length) {
return {error: response.errors[0].message};
}
const channelsFetchedData = response.data;
const chData = {
channels: getMemberChannelsFromGQLQuery(channelsFetchedData),
memberships: channelsFetchedData.channelMembers?.map((m) => gqlToClientChannelMembership(m, userId)),
categories: channelsFetchedData.sidebarCategories?.map((c) => gqlToClientSidebarCategory(c, '')),
};
const roles = channelsFetchedData.channelMembers?.map((m) => m.roles).flat() || [];
return {chData, roles};
};
export const entryGQL = async (serverUrl: string, currentTeamId?: string, currentChannelId?: string): Promise<EntryResponse> => {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
const {database} = operator;
let response;
try {
response = await gqlEntry(serverUrl);
} catch (error) {
return {error: getFullErrorMessage(error)};
}
if ('error' in response) {
return {error: response.error};
}
if ('errors' in response && response.errors?.length) {
return {error: response.errors[0].message};
}
const fetchedData = response.data;
const config = fetchedData.config || {} as ClientConfig;
const license = fetchedData.license || {} as ClientLicense;
await storeConfigAndLicense(serverUrl, config, license);
const meData = {
user: gqlToClientUser(fetchedData.user!),
};
const allTeams = getMemberTeamsFromGQLQuery(fetchedData);
const allTeamMemberships = fetchedData.teamMembers.map((m) => gqlToClientTeamMembership(m, meData.user.id));
const [nonArchivedTeams, archivedTeamIds] = allTeams.reduce((acc, t) => {
if (t.delete_at) {
acc[1].add(t.id);
return acc;
}
return [[...acc[0], t], acc[1]];
}, [[], new Set<string>()]);
const nonArchivedTeamMemberships = allTeamMemberships.filter((m) => !archivedTeamIds.has(m.team_id));
const teamData = {
teams: nonArchivedTeams,
memberships: nonArchivedTeamMemberships,
};
const prefData = {
preferences: fetchedData.user?.preferences?.map(gqlToClientPreference),
};
if (prefData.preferences) {
const crtToggled = await getHasCRTChanged(database, prefData.preferences);
if (crtToggled) {
const {error} = await truncateCrtRelatedTables(serverUrl);
if (error) {
return {error: `Resetting CRT on ${serverUrl} failed`};
}
}
}
let initialTeamId = currentTeamId;
if (!teamData.teams.length) {
initialTeamId = '';
} else if (!initialTeamId || !teamData.teams.find((t) => t.id === currentTeamId && t.delete_at === 0)) {
const teamOrderPreference = getPreferenceValue<string>(prefData.preferences || [], Preferences.CATEGORIES.TEAMS_ORDER, '', '');
initialTeamId = selectDefaultTeam(teamData.teams, meData.user.locale, teamOrderPreference, config.ExperimentalPrimaryTeam)?.id || '';
}
const gqlRoles = [
...fetchedData.user?.roles || [],
...fetchedData.teamMembers?.map((m) => m.roles).flat() || [],
];
let chData;
if (initialTeamId) {
const result = await getChannelData(serverUrl, initialTeamId, meData.user.id, false);
if ('error' in result) {
return result;
}
chData = result.chData;
gqlRoles.push(...result.roles);
}
const roles = filterAndTransformRoles(gqlRoles);
const initialChannelId = await entryInitialChannelId(database, currentChannelId, currentTeamId, initialTeamId, meData.user.id, chData?.channels, chData?.memberships);
const removeChannels = await getRemoveChannels(database, chData, initialTeamId, true);
const removeTeamIds = await getRemoveTeamIds(database, teamData);
const removeTeams = await teamsToRemove(serverUrl, removeTeamIds);
const modelPromises = await prepareModels({operator, initialTeamId, removeTeams, removeChannels, teamData, chData, prefData, meData}, true);
if (roles.length) {
modelPromises.push(operator.handleRole({roles, prepareRecordsOnly: true}));
}
const models = (await Promise.all(modelPromises)).flat();
return {models, initialTeamId, initialChannelId, prefData, teamData, chData, meData};
};
export const entry = async (serverUrl: string, teamId?: string, channelId?: string, since = 0): Promise<EntryResponse> => {
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const config = await getConfig(database);
let result;
if (config?.FeatureFlagGraphQL === 'true') {
result = await entryGQL(serverUrl, teamId, channelId);
if ('error' in result) {
logDebug('Error using GraphQL, trying REST', result.error);
result = entryRest(serverUrl, teamId, channelId, since);
}
} else {
result = entryRest(serverUrl, teamId, channelId, since);
}
// Fetch data retention policies
const isDataRetentionEnabled = await getIsDataRetentionEnabled(database);
if (isDataRetentionEnabled) {
fetchDataRetentionPolicy(serverUrl);
}
return result;
};
export async function deferredAppEntryActions(
serverUrl: string, since: number, currentUserId: string, currentUserLocale: string, preferences: PreferenceType[] | undefined,
config: ClientConfig, license: ClientLicense | undefined, teamData: MyTeamsRequest, chData: MyChannelsRequest | undefined,
initialTeamId?: string, initialChannelId?: string) {
let result;
if (config?.FeatureFlagGraphQL === 'true') {
result = await deferredAppEntryGraphQLActions(serverUrl, since, currentUserId, teamData, chData, preferences, config, initialTeamId, initialChannelId);
if (result.error) {
logDebug('Error using GraphQL, trying REST', result.error);
result = restDeferredAppEntryActions(serverUrl, since, currentUserId, currentUserLocale, preferences, config, license, teamData, chData, initialTeamId, initialChannelId);
}
} else {
result = restDeferredAppEntryActions(serverUrl, since, currentUserId, currentUserLocale, preferences, config, license, teamData, chData, initialTeamId, initialChannelId);
}
autoUpdateTimezone(serverUrl);
return result;
}

View file

@ -4,8 +4,7 @@
import {markChannelAsViewed} from '@actions/local/channel';
import {dataRetentionCleanup} from '@actions/local/systems';
import {markChannelAsRead} from '@actions/remote/channel';
import {handleEntryAfterLoadNavigation, registerDeviceToken} from '@actions/remote/entry/common';
import {deferredAppEntryActions, entry} from '@actions/remote/entry/gql_common';
import {deferredAppEntryActions, entry, handleEntryAfterLoadNavigation, registerDeviceToken} from '@actions/remote/entry/common';
import {fetchPostsForChannel, fetchPostThread} from '@actions/remote/post';
import {openAllUnreadChannels} from '@actions/remote/preference';
import {autoUpdateTimezone} from '@actions/remote/user';

View file

@ -1,16 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
export const QUERY_ENTRY = 'gql_m_entry';
export const QUERY_CHANNELS = 'gql_m_channels';
export const QUERY_CHANNELS_NEXT = 'gql_m_channels_next';
export const QUERY_ALL_CHANNELS = 'gql_m_all_channels';
export const QUERY_ALL_CHANNELS_NEXT = 'gql_m_all_channels_next';
export default {
QUERY_ENTRY,
QUERY_CHANNELS,
QUERY_CHANNELS_NEXT,
QUERY_ALL_CHANNELS,
QUERY_ALL_CHANNELS_NEXT,
};

View file

@ -1,392 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {MEMBERS_PER_PAGE} from '@constants/graphql';
import NetworkManager from '@managers/network_manager';
import {getFullErrorMessage} from '@utils/errors';
import {logDebug} from '@utils/log';
import QueryNames from './constants';
const doGQLQuery = async (serverUrl: string, query: string, variables: {[name: string]: any}, operationName: string) => {
try {
const client = NetworkManager.getClient(serverUrl);
const response = await client.doFetch('/api/v5/graphql', {method: 'post', body: {query, variables, operationName}}) as GQLResponse;
return response;
} catch (error) {
logDebug('error on doGQLQuery', getFullErrorMessage(error));
return {error};
}
};
export const gqlEntry = async (serverUrl: string) => {
return doGQLQuery(serverUrl, entryQuery, {}, QueryNames.QUERY_ENTRY);
};
export const gqlEntryChannels = async (serverUrl: string, teamId: string) => {
const variables = {
teamId,
exclude: false,
perPage: MEMBERS_PER_PAGE,
};
const response = await doGQLQuery(serverUrl, channelsQuery, variables, QueryNames.QUERY_CHANNELS);
if ('error' in response || response.errors) {
return response;
}
let members = response.data.channelMembers;
while (members?.length === MEMBERS_PER_PAGE) {
let pageResponse;
try {
// eslint-disable-next-line no-await-in-loop
pageResponse = await gqlEntryChannelsNextPage(serverUrl, teamId, members[members.length - 1].cursor!, false);
} catch {
break;
}
if ('error' in pageResponse) {
break;
}
members = pageResponse.data.channelMembers!;
response.data.channelMembers?.push(...members);
}
return response;
};
const gqlEntryChannelsNextPage = async (serverUrl: string, teamId: string, cursor: string, exclude: boolean) => {
const variables = {
teamId,
exclude,
perPage: MEMBERS_PER_PAGE,
cursor,
};
return doGQLQuery(serverUrl, nextPageChannelsQuery, variables, QueryNames.QUERY_CHANNELS_NEXT);
};
export const gqlOtherChannels = async (serverUrl: string, teamId: string) => {
const variables = {
teamId,
exclude: true,
perPage: MEMBERS_PER_PAGE,
};
const response = await doGQLQuery(serverUrl, channelsQuery, variables, QueryNames.QUERY_CHANNELS);
if ('error' in response || response.errors) {
return response;
}
let members = response.data.channelMembers;
while (members?.length === MEMBERS_PER_PAGE) {
let pageResponse;
try {
// eslint-disable-next-line no-await-in-loop
pageResponse = await gqlEntryChannelsNextPage(serverUrl, teamId, members[members.length - 1].cursor!, true);
} catch {
break;
}
if ('error' in pageResponse || 'errors' in pageResponse) {
break;
}
members = pageResponse.data.channelMembers!;
response.data.channelMembers?.push(...members);
}
return response;
};
export const gqlAllChannels = async (serverUrl: string) => {
const variables = {
perPage: MEMBERS_PER_PAGE,
};
const response = await doGQLQuery(serverUrl, allChannelsQuery, variables, QueryNames.QUERY_ALL_CHANNELS);
if ('error' in response || response.errors) {
return response;
}
let members = response.data.channelMembers;
while (members?.length === MEMBERS_PER_PAGE) {
let pageResponse;
try {
// eslint-disable-next-line no-await-in-loop
pageResponse = await gqlAllChannelsNextPage(serverUrl, members[members.length - 1].cursor!);
} catch {
break;
}
if ('error' in pageResponse || 'errors' in pageResponse) {
break;
}
members = pageResponse.data.channelMembers!;
response.data.channelMembers?.push(...members);
}
return response;
};
const gqlAllChannelsNextPage = async (serverUrl: string, cursor: string) => {
const variables = {
perPage: MEMBERS_PER_PAGE,
cursor,
};
return doGQLQuery(serverUrl, nextPageAllChannelsQuery, variables, QueryNames.QUERY_ALL_CHANNELS_NEXT);
};
const entryQuery = `
query ${QueryNames.QUERY_ENTRY} {
config
license
user(id:"me") {
id
createAt
updateAt
deleteAt
username
authService
email
emailVerified
nickname
firstName
lastName
position
roles {
id
name
permissions
}
locale
notifyProps
props
timezone
isBot
lastPictureUpdate
remoteId
status {
status
}
botDescription
botLastIconUpdate
preferences{
category
name
value
userId
}
sessions {
createAt
expiresAt
}
termsOfServiceId
termsOfServiceCreateAt
}
teamMembers(userId:"me") {
deleteAt
schemeAdmin
roles {
id
name
permissions
}
team {
id
description
displayName
name
type
allowedDomains
lastTeamIconUpdate
groupConstrained
allowOpenInvite
createAt
updateAt
deleteAt
schemeId
policyId
cloudLimitsArchived
}
}
}
`;
const channelsQuery = `
query ${QueryNames.QUERY_CHANNELS}($teamId: String!, $perPage: Int!, $exclude: Boolean!) {
channelMembers(userId:"me", first:$perPage, teamId:$teamId, excludeTeam:$exclude) {
cursor
msgCount
msgCountRoot
mentionCount
mentionCountRoot
schemeAdmin
lastViewedAt
notifyProps
roles {
id
name
permissions
}
channel {
id
header
purpose
type
createAt
updateAt
creatorId
deleteAt
displayName
prettyDisplayName
groupConstrained
name
shared
lastPostAt
totalMsgCount
totalMsgCountRoot
lastRootPostAt
team {
id
}
}
}
sidebarCategories(userId:"me", teamId:$teamId, excludeTeam:$exclude) {
displayName
id
sortOrder
sorting
type
muted
collapsed
channelIds
teamId
}
}
`;
const nextPageChannelsQuery = `
query ${QueryNames.QUERY_CHANNELS_NEXT}($teamId: String!, $perPage: Int!, $exclude: Boolean!, $cursor: String!) {
channelMembers(userId:"me", first:$perPage, after:$cursor, teamId:$teamId, excludeTeam:$exclude) {
cursor
msgCount
msgCountRoot
mentionCount
mentionCountRoot
schemeAdmin
lastViewedAt
notifyProps
roles {
id
name
permissions
}
channel {
id
header
purpose
type
createAt
updateAt
creatorId
deleteAt
displayName
prettyDisplayName
groupConstrained
name
shared
lastPostAt
totalMsgCount
totalMsgCountRoot
lastRootPostAt
team {
id
}
}
}
}
`;
const allChannelsQuery = `
query ${QueryNames.QUERY_ALL_CHANNELS}($perPage: Int!){
channelMembers(userId:"me", first:$perPage) {
cursor
msgCount
msgCountRoot
mentionCount
mentionCountRoot
schemeAdmin
lastViewedAt
notifyProps
roles {
id
name
permissions
}
channel {
id
header
purpose
type
createAt
updateAt
creatorId
deleteAt
displayName
prettyDisplayName
groupConstrained
name
shared
lastPostAt
totalMsgCount
totalMsgCountRoot
lastRootPostAt
team {
id
}
}
}
}
`;
const nextPageAllChannelsQuery = `
query ${QueryNames.QUERY_ALL_CHANNELS_NEXT}($perPage: Int!, $cursor: String!) {
channelMembers(userId:"me", first:$perPage, after:$cursor) {
cursor
msgCount
msgCountRoot
mentionCount
mentionCountRoot
schemeAdmin
lastViewedAt
notifyProps
roles {
id
name
permissions
}
channel {
id
header
purpose
type
createAt
updateAt
creatorId
deleteAt
displayName
prettyDisplayName
groupConstrained
name
shared
lastPostAt
totalMsgCount
totalMsgCountRoot
lastRootPostAt
team {
id
}
}
}
}
`;

View file

@ -1,4 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
export const MEMBERS_PER_PAGE = 200;

View file

@ -67,17 +67,17 @@ export function prepareMissingChannelsForAllTeams(operator: ServerDataOperator,
}
}
export const prepareMyChannelsForTeam = async (operator: ServerDataOperator, teamId: string, channels: Channel[], channelMembers: ChannelMembership[], isCRTEnabled?: boolean, isGraphQL = false) => {
export const prepareMyChannelsForTeam = async (operator: ServerDataOperator, teamId: string, channels: Channel[], channelMembers: ChannelMembership[], isCRTEnabled?: boolean) => {
const {database} = operator;
const channelsQuery = isGraphQL ? queryAllChannels(database) : queryAllChannelsForTeam(database, teamId);
const channelsQuery = queryAllChannelsForTeam(database, teamId);
const allChannelsForTeam = (await channelsQuery.fetch()).
reduce((map: Record<string, ChannelModel>, channel) => {
map[channel.id] = channel;
return map;
}, {});
const channelInfosQuery = isGraphQL ? queryAllChannelsInfo(database) : queryAllChannelsInfoForTeam(database, teamId);
const channelInfosQuery = queryAllChannelsInfoForTeam(database, teamId);
const allChannelsInfoForTeam = (await channelInfosQuery.fetch()).
reduce((map: Record<string, ChannelInfoModel>, info) => {
map[info.id] = info;

View file

@ -43,7 +43,7 @@ const {
MY_CHANNEL,
} = MM_TABLES.SERVER;
export async function prepareModels({operator, initialTeamId, removeTeams, removeChannels, teamData, chData, prefData, meData, isCRTEnabled}: PrepareModelsArgs, isGraphQL = false): Promise<Array<Promise<Model[]>>> {
export async function prepareModels({operator, initialTeamId, removeTeams, removeChannels, teamData, chData, prefData, meData, isCRTEnabled}: PrepareModelsArgs): Promise<Array<Promise<Model[]>>> {
const modelPromises: Array<Promise<Model[]>> = [];
if (removeTeams?.length) {
@ -67,10 +67,8 @@ export async function prepareModels({operator, initialTeamId, removeTeams, remov
}
if (chData?.channels?.length && chData.memberships?.length) {
if (isGraphQL) {
modelPromises.push(...await prepareMyChannelsForTeam(operator, '', chData.channels, chData.memberships, isCRTEnabled, true));
} else if (initialTeamId) {
modelPromises.push(...await prepareMyChannelsForTeam(operator, initialTeamId, chData.channels, chData.memberships, isCRTEnabled, false));
if (initialTeamId) {
modelPromises.push(...await prepareMyChannelsForTeam(operator, initialTeamId, chData.channels, chData.memberships, isCRTEnabled));
}
}

View file

@ -1,215 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
const defaultNotifyProps: UserNotifyProps = {
channel: 'true',
comments: 'never',
desktop: 'mention',
desktop_sound: 'true',
email: 'true',
email_threads: 'all',
first_name: 'false',
mention_keys: '',
push: 'mention',
push_status: 'away',
push_threads: 'all',
};
export const gqlToClientUser = (u: Partial<GQLUser>): UserProfile => {
return {
id: u.id || '',
create_at: u.createAt || 0,
update_at: u.updateAt || 0,
delete_at: u.deleteAt || 0,
username: u.username || '',
auth_service: u.authService || '',
email: u.email || '',
email_verified: u.emailVerified ?? true,
nickname: u.nickname || '',
first_name: u.firstName || '',
last_name: u.lastName || '',
position: u.position || '',
roles: u.roles?.map((v) => v.name!).join(' ') || '',
locale: u.locale || '',
notify_props: u.notifyProps || defaultNotifyProps,
props: u.props,
timezone: u.timezone,
is_bot: u.isBot,
last_picture_update: u.lastPictureUpdate,
remote_id: u.remoteId,
status: u.status?.status || '',
bot_description: u.botDescription,
bot_last_icon_update: u.botLastIconUpdate,
terms_of_service_id: u.termsOfServiceId || '',
terms_of_service_create_at: u.termsOfServiceCreateAt || 0,
auth_data: '',
};
};
export const gqlToClientSession = (s: Partial<GQLSession>): Session => {
return {
create_at: s.createAt || 0,
expires_at: s.expiresAt || 0,
id: '',
user_id: '',
};
};
export const gqlToClientTeamMembership = (m: Partial<GQLTeamMembership>, userId?: string): TeamMembership => {
return {
team_id: m.team?.id || '',
delete_at: m.deleteAt || 0,
roles: m.roles?.map((v) => v.name!).join(' ') || '',
user_id: m.user?.id || userId || '',
scheme_admin: m.schemeAdmin || false,
scheme_user: m.schemeUser || false,
mention_count: 0,
msg_count: 0,
};
};
export const gqlToClientSidebarCategory = (c: Partial<GQLSidebarCategory>, teamId: string): CategoryWithChannels => {
return {
channel_ids: c.channelIds || [],
collapsed: c.collapsed || false,
display_name: c.displayName || '',
id: c.id || '',
muted: c.muted || false,
sort_order: c.sortOrder || 0,
sorting: c.sorting || 'alpha',
team_id: c.teamId || teamId,
type: c.type || 'channels',
};
};
export const gqlToClientTeam = (t: Partial<GQLTeam>): Team => {
return {
allow_open_invite: t.allowOpenInvite || false,
allowed_domains: t.allowedDomains || '',
company_name: t.companyName || '',
create_at: t.createAt || 0,
delete_at: t.deleteAt || 0,
description: t.description || '',
display_name: t.displayName || '',
email: t.email || '',
group_constrained: t.groupConstrained || false,
id: t.id || '',
invite_id: t.inviteId || '',
last_team_icon_update: t.lastTeamIconUpdate || 0,
name: t.name || '',
scheme_id: t.schemeId || '',
type: t.type || 'I',
update_at: t.updateAt || 0,
// cloudLimitsArchived and policyId not used
};
};
export const gqlToClientPreference = (p: Partial<GQLPreference>): PreferenceType => {
return {
category: p.category || '',
name: p.name || '',
user_id: p.userId || '',
value: p.value || '',
};
};
export const gqlToClientChannelMembership = (m: Partial<GQLChannelMembership>, userId?: string): ChannelMembership => {
return {
channel_id: m.channel?.id || '',
last_update_at: m.lastUpdateAt || 0,
last_viewed_at: m.lastViewedAt || 0,
mention_count: m.mentionCount || 0,
mention_count_root: m.mentionCountRoot || 0,
msg_count: m.msgCount || 0,
msg_count_root: m.msgCountRoot || 0,
notify_props: m.notifyProps || {},
roles: m.roles?.map((r) => r.name).join(' ') || '',
user_id: m.user?.id || userId || '',
is_unread: false,
last_post_at: 0,
post_root_id: '',
scheme_admin: m.schemeAdmin,
scheme_user: m.schemeUser,
};
};
export const gqlToClientChannel = (c: Partial<GQLChannel>, teamId?: string): Channel => {
return {
create_at: c.createAt || 0,
creator_id: c.creatorId || '',
delete_at: c.deleteAt || 0,
display_name: c.prettyDisplayName || c.displayName || '',
extra_update_at: 0,
group_constrained: c.groupConstrained || false,
header: c.header || '',
id: c.id || '',
last_post_at: c.lastPostAt || 0,
last_root_post_at: c.lastRootPostAt || 0,
name: c.name || '',
purpose: c.purpose || '',
scheme_id: c.schemeId || '',
shared: c.shared || false,
team_id: c.team?.id || teamId || '',
total_msg_count: c.totalMsgCount || 0,
total_msg_count_root: c.totalMsgCountRoot || 0,
type: c.type || 'O',
update_at: c.updateAt || 0,
fake: false,
isCurrent: false,
status: '',
teammate_id: '',
};
};
export const gqlToClientChannelStats = (s: Partial<GQLChannel>): ChannelStats => {
return {
channel_id: s.id || '',
guest_count: s.stats?.guestCount || 0,
member_count: s.stats?.memberCount || 0,
pinnedpost_count: s.stats?.pinnePostCount || 0,
files_count: s.stats?.filesCount || 0,
};
};
export const gqlToClientRole = (r: Partial<GQLRole>): Role => {
return {
id: r.id || '',
name: r.name || '',
permissions: r.permissions || [],
built_in: r.builtIn,
scheme_managed: r.schemeManaged,
};
};
export const getMemberChannelsFromGQLQuery = (data: GQLData) => {
return data.channelMembers?.reduce<Channel[]>((acc, m) => {
if (m.channel) {
acc.push(gqlToClientChannel(m.channel));
}
return acc;
}, []);
};
export const getMemberTeamsFromGQLQuery = (data: GQLData) => {
return data.teamMembers?.reduce<Team[]>((acc, m) => {
if (m.team) {
acc.push(gqlToClientTeam(m.team));
}
return acc;
}, []);
};
export const filterAndTransformRoles = (roles: Array<Partial<GQLRole> | undefined>) => {
const byName = roles.reduce<{[name: string]: Partial<GQLRole>}>((acum, r) => {
if (r?.name && !acum[r.name]) {
acum[r.name] = r;
}
return acum;
}, {});
return Object.values(byName).map((r) => gqlToClientRole(r));
};

View file

@ -119,7 +119,6 @@ interface ClientConfig {
ExtendSessionLengthWithActivity: string;
FeatureFlagAppsEnabled?: string;
FeatureFlagCollapsedThreads?: string;
FeatureFlagGraphQL?: string;
FeatureFlagPostPriority?: string;
ForgotPasswordLink?: string;
GfycatApiKey: string;

186
types/api/graphql.d.ts vendored
View file

@ -1,186 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
type GQLResponse = {
errors?: GQLError[];
data: GQLData;
}
type GQLData = {
user?: Partial<GQLUser>;
config?: ClientConfig;
license?: ClientLicense;
teamMembers: Array<Partial<GQLTeamMembership>>;
channels?: Array<Partial<GQLChannel>>;
channelsLeft?: Array<Partial<GQLChannel>>;
channelMembers?: Array<Partial<GQLChannelMembership>>;
sidebarCategories?: Array<Partial<GQLSidebarCategory>>;
}
type GQLError = {
message: string;
path: Array<string | number>;
}
type GQLUser = {
id: string;
createAt: number;
updateAt: number;
deleteAt: number;
username: string;
authService: string;
email: string;
emailVerified: boolean;
nickname: string;
firstName: string;
lastName: string;
position: string;
locale: string;
notifyProps: UserNotifyProps;
props: UserProps;
timezone: UserTimezone;
isBot: boolean;
lastPictureUpdate: number;
remoteId: string;
botDescription: string;
botLastIconUpdate: number;
termsOfServiceId: string;
termsOfServiceCreateAt: number;
roles: Array<Partial<GQLRole>>;
customStatus: Partial<GQLUserCustomStatus>;
status: Partial<GQLUserStatus>;
preferences: Array<Partial<GQLPreference>>;
sessions: Array<Partial<GQLSession>>;
// Derived
isSystemAdmin: boolean;
isGuest: boolean;
}
type GQLSession = {
createAt: number;
expiresAt: number;
}
type GQLTeamMembership = {
team: Partial<GQLTeam>;
user: Partial<GQLUser>;
roles: Array<Partial<GQLRole>>;
deleteAt: number;
schemeGuest: boolean;
schemeUser: boolean;
schemeAdmin: boolean;
}
type GQLSidebarCategory = {
id: string;
sorting: CategorySorting;
type: CategoryType;
displayName: string;
muted: boolean;
collapsed: boolean;
channelIds: string[];
sortOrder: number;
teamId: string;
}
type GQLTeam = {
id: string;
displayName: string;
name: string;
description: string;
email: string;
type: TeamType;
companyName: string;
allowedDomains: string;
inviteId: string;
lastTeamIconUpdate: number;
groupConstrained: boolean;
allowOpenInvite: boolean;
updateAt: number;
createAt: number;
deleteAt: number;
schemeId: string;
policyId: string;
cloudLimitsArchived: boolean;
}
type GQLUserCustomStatus = {
emoji: string;
text: string;
duration: CustomStatusDuration;
expiresAt: string;
}
type GQLUserStatus = {
status: string;
manual: boolean;
lastActivityAt: number;
activeChannel: string;
dndEndTime: number;
}
type GQLPreference = {
userId: string;
category: string;
name: string;
value: string;
}
type GQLChannelMembership = {
channel: Partial<GQLChannel>;
user: Partial<GQLUser>;
roles: Array<Partial<GQLRole>>;
lastViewedAt: number;
msgCount: number;
msgCountRoot: number;
mentionCount: number;
mentionCountRoot: number;
notifyProps: ChannelNotifyProps;
lastUpdateAt: number;
schemeGuest: boolean;
schemeUser: boolean;
schemeAdmin: boolean;
explicitRoles: string;
cursor: string;
}
type GQLChannel = {
id: string;
createAt: number;
updateAt: number;
deleteAt: number;
type: ChannelType;
displayName: string;
prettyDisplayName: string;
name: string;
header: string;
purpose: string;
creatorId: string;
schemeId: string;
team: Partial<GQLTeam>;
cursor: string;
groupConstrained: boolean;
shared: boolean;
lastPostAt: number;
lastRootPostAt: number;
totalMsgCount: number;
totalMsgCountRoot: number;
stats: Partial<GQLStats>;
}
type GQLStats = {
guestCount: number;
memberCount: number;
pinnePostCount: number;
filesCount: number;
}
type GQLRole = {
id: string;
name: string;
permissions: string[];
schemeManaged: boolean;
builtIn: boolean;
}