mattermost-mobile/app/actions/remote/post.ts
Kyriakos Z 675d8495b3
[Gekidou]: MM-39757 - Recent mentions (#5823)
* [Gekidou]: MM-39757 - Recent mentions

* Refactor channel_info to a separate component

* Fixes schema tests

* Fixes channel_info theme color

* Removes RECEIVED_MENTIONS table and model

Removes RECEIVED_MENTIONS table and model and saves recent_mentions in
the SYSTEM table under the recentMentions ID.

* Cleanup recent_mentions handler

* Adds i18n in recent_mentions screen

* Observe changes on the post messages

* Addresses review comments

* Batches records

* Addresses review comments

* Addresses review comments

* Addresses review comments

* Addresses review comments

* Fetches channels and users needed for mentions

Fetching mentions from all teams might result in missing info like user
profiles, and channels missing from the DB.
This commit fetches all missing users and channels.

* Adds empty state for recent mentions

* Prepares all missing models for channels

* Addresses review comments

* Fixes mention keys for recent mentions

User mention keys when asking for mentions should not include general
purpose ones, like @channel, @all, @here.

Fixes ActivityIndicator color in recent mentions screen.

* Removes top margin of mention message

* Addresses review comments

* Fixes group.name undefined
2021-12-16 12:26:43 +02:00

294 lines
9.6 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
//
import Model from '@nozbe/watermelondb/Model';
import {processPostsFetched} from '@actions/local/post';
import {ActionType, General} from '@constants';
import {SYSTEM_IDENTIFIERS} from '@constants/database';
import DatabaseManager from '@database/manager';
import {getNeededAtMentionedUsernames} from '@helpers/api/user';
import NetworkManager from '@init/network_manager';
import {prepareMissingChannelsForAllTeams, queryAllMyChannelIds} from '@queries/servers/channel';
import {queryRecentPostsInChannel} from '@queries/servers/post';
import {queryCurrentUserId, queryCurrentChannelId} from '@queries/servers/system';
import {queryAllUsers} from '@queries/servers/user';
import {forceLogoutIfNecessary} from './session';
import type {Client} from '@client/rest';
type PostsRequest = {
error?: unknown;
order?: string[];
posts?: Post[];
previousPostId?: string;
}
type AuthorsRequest = {
authors?: UserProfile[];
error?: unknown;
}
export const fetchPostsForCurrentChannel = async (serverUrl: string) => {
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
if (!database) {
return {error: `${serverUrl} database not found`};
}
const currentChannelId = await queryCurrentChannelId(database);
return fetchPostsForChannel(serverUrl, currentChannelId);
};
export const fetchPostsForChannel = async (serverUrl: string, channelId: string) => {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
let postAction: Promise<PostsRequest>|undefined;
let actionType: string|undefined;
const postsInChannel = await queryRecentPostsInChannel(operator.database, channelId);
if (!postsInChannel || postsInChannel.length < General.POST_CHUNK_SIZE) {
postAction = fetchPosts(serverUrl, channelId, 0, General.POST_CHUNK_SIZE, true);
actionType = ActionType.POSTS.RECEIVED_IN_CHANNEL;
} else {
const since = postsInChannel[0]?.createAt || 0;
postAction = fetchPostsSince(serverUrl, channelId, since, true);
actionType = ActionType.POSTS.RECEIVED_SINCE;
}
const data = await postAction;
if (data.error) {
// Here we should emit an event that fetching posts failed.
}
if (data.posts?.length && data.order?.length) {
try {
await fetchPostAuthors(serverUrl, data.posts, false);
} catch (error) {
// eslint-disable-next-line no-console
console.log('FETCH AUTHORS ERROR', error);
}
operator.handlePosts({
actionType,
order: data.order,
posts: data.posts,
previousPostId: data.previousPostId,
});
}
return {posts: data.posts};
};
export const fetchPostsForUnreadChannels = async (serverUrl: string, channels: Channel[], memberships: ChannelMembership[], excludeChannelId?: string) => {
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
if (!database) {
return {error: `${serverUrl} database not found`};
}
try {
for (const member of memberships) {
const channel = channels.find((c) => c.id === member.channel_id);
if (channel && (channel.total_msg_count - member.msg_count) > 0 && channel.id !== excludeChannelId) {
fetchPostsForChannel(serverUrl, channel.id);
}
}
} catch (error) {
return {error};
}
return {error: undefined};
};
export const fetchPosts = async (serverUrl: string, channelId: string, page = 0, perPage = General.POST_CHUNK_SIZE, fetchOnly = false): Promise<PostsRequest> => {
let client: Client;
try {
client = NetworkManager.getClient(serverUrl);
} catch (error) {
return {error};
}
try {
const data = await client.getPosts(channelId, page, perPage);
return processPostsFetched(serverUrl, ActionType.POSTS.RECEIVED_IN_CHANNEL, data, fetchOnly);
} catch (error) {
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
return {error};
}
};
export const fetchPostsSince = async (serverUrl: string, channelId: string, since: number, fetchOnly = false): Promise<PostsRequest> => {
let client: Client;
try {
client = NetworkManager.getClient(serverUrl);
} catch (error) {
return {error};
}
try {
const data = await client.getPostsSince(channelId, since);
return processPostsFetched(serverUrl, ActionType.POSTS.RECEIVED_SINCE, data, fetchOnly);
} catch (error) {
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
return {error};
}
};
export const fetchPostAuthors = async (serverUrl: string, posts: Post[], fetchOnly = false): Promise<AuthorsRequest> => {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
let client: Client;
try {
client = NetworkManager.getClient(serverUrl);
} catch (error) {
return {error};
}
const currentUserId = await queryCurrentUserId(operator.database);
const users = await queryAllUsers(operator.database);
const existingUserIds = new Set<string>();
const existingUserNames = new Set<string>();
let excludeUsername;
users.forEach((u) => {
existingUserIds.add(u.id);
existingUserNames.add(u.username);
if (u.id === currentUserId) {
excludeUsername = u.username;
}
});
const usernamesToLoad = getNeededAtMentionedUsernames(existingUserNames, posts, excludeUsername);
const userIdsToLoad = new Set<string>();
posts.forEach((p) => {
const userId = p.user_id;
if (userId === currentUserId) {
return;
}
if (!existingUserIds.has(userId)) {
userIdsToLoad.add(userId);
}
});
try {
const promises: Array<Promise<UserProfile[]>> = [];
if (userIdsToLoad.size) {
promises.push(client.getProfilesByIds(Array.from(userIdsToLoad)));
}
if (usernamesToLoad.size) {
promises.push(client.getProfilesByUsernames(Array.from(usernamesToLoad)));
}
if (promises.length) {
const result = await Promise.all(promises);
const authors = result.flat();
if (!fetchOnly && authors.length) {
await operator.handleUsers({
users: authors,
prepareRecordsOnly: false,
});
}
return {authors};
}
return {authors: [] as UserProfile[]};
} catch (error) {
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
return {error};
}
};
export const postActionWithCookie = async (serverUrl: string, postId: string, actionId: string, actionCookie: string, selectedOption = '') => {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
let client: Client;
try {
client = NetworkManager.getClient(serverUrl);
} catch (error) {
return {error};
}
try {
const data = await client.doPostActionWithCookie(postId, actionId, actionCookie, selectedOption);
if (data?.trigger_id) {
await operator.handleSystem({
systems: [{
id: SYSTEM_IDENTIFIERS.INTEGRATION_TRIGGER_ID,
value: data.trigger_id,
}],
prepareRecordsOnly: false,
});
}
return {data};
} catch (error) {
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
return {error};
}
};
export async function getMissingChannelsFromPosts(serverUrl: string, posts: Post[], fetchOnly = false) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
let client: Client;
try {
client = NetworkManager.getClient(serverUrl);
} catch (error) {
return {error};
}
const channelIds = await queryAllMyChannelIds(operator.database);
const channelPromises: Array<Promise<Channel>> = [];
const userPromises: Array<Promise<ChannelMembership>> = [];
posts.forEach((post) => {
const id = post.channel_id;
if (channelIds.indexOf(id) === -1) {
channelPromises.push(client.getChannel(id));
userPromises.push(client.getMyChannelMember(id));
}
});
const channels = await Promise.all(channelPromises);
const channelMemberships = await Promise.all(userPromises);
if (!fetchOnly && channels.length && channelMemberships.length) {
const modelPromises = prepareMissingChannelsForAllTeams(operator, channels, channelMemberships) as Array<Promise<Model[]>>;
if (modelPromises && modelPromises.length) {
const channelModelsArray = await Promise.all(modelPromises);
if (channelModelsArray.length) {
const models = channelModelsArray.flatMap((mdls) => {
if (!mdls || mdls.length) {
return [];
}
return mdls;
});
if (models) {
operator.batchRecords(models);
}
}
}
}
return {
channels,
channelMemberships,
};
}