Merging performance branch into master (#4268)

* improve performance on sendNotifications

* Fix SQL queries

* Remove get direct profiles, not needed anymore

* Add raw data to error details if AppError fails to decode

* men

* Fix decode (#4052)

* Fixing json decode

* Adding unit test

* Initial work for client scaling (#4051)

* Begin adding paging to profiles API

* Added more paging functionality

* Finish hooking up admin console user lists

* Add API for searching users and add searching to all user lists

* Add lazy loading of profiles

* Revert config.json

* Fix unit tests and some style issues

* Add GetProfilesFromList to Go driver and fix web unit test

* Update etag for GetProfiles

* Updating ui for filters and pagination (#4044)

* Updating UI for pagination

* Adjusting margins for filter row

* Adjusting margin for specific modals

* Adding relative padding to system console

* Adjusting responsive view

* Update client user tests

* Minor fixes for direct messages modal (#4056)

* Remove some unneeded initial load calls (#4057)

* UX updates to user lists, added smart counts and bug fixes (#4059)

* Improved getExplicitMentions and unit tests (#4064)

* Refactor getting posts to lazy load profiles correctly (#4062)

* Comment out SetActiveChannel test (#4066)

* Profiler cpu, block, and memory profiler. (#4081)

* Fix TestSetActiveChannel unit test (#4071)

* Fixing build failure caused by dependancies updating (#4076)

* Adding profiler

* Fix admin_team_member_dropdown eslint errors

* Bumping session cache size (#4077)

* Bumping session cache size

* Bumping status cache

* Refactor how the client handles channel members to be large team friendly (#4106)

* Refactor how the client handles channel members to be large team friendly

* Change Id to ChannelId in ChannelStats model

* Updated getChannelMember and getProfilesByIds routes to match proposal

* Performance improvements (#4100)

* Performance improvements

* Fixing re-connect issue

* Fixing error message

* Some other minor perf tweaks

* Some other minor perf tweaks

* Fixing config file

* Fixing buffer size

* Fixing web socket send message

* adding some error logging

* fix getMe to be user required

* Fix websocket event for new user

* Fixing shutting down

* Reverting web socket changes

* Fixing logging lvl

* Adding caching to GetMember

* Adding some logging

* Fixing caching

* Fixing caching invalidate

* Fixing direct message caching

* Fixing caching

* Fixing caching

* Remove GetDirectProfiles from initial load

* Adding logging and fixing websocket client

* Adding back caching from bad merge.

* Explicitly close go driver requests (#4162)

* Refactored how the client handles team members to be more large team friendly (#4159)

* Refactor getProfilesForDirectMessageList API into getAllProfiles API

* Refactored how the client handles team members to be more large team friendly

* Fix js error when receiving a notification

* Fix JS error caused by current user being overwritten with sanitized version (#4165)

* Adding error message to status failure (#4167)

* Fix a few bugs caused by client scaling refactoring (#4170)

* When there is no read replica, don't open a second set of connections to the master database (#4173)

* Adding connection tacking to stats (#4174)

* Reduce DB writes for statuses and other status related changes (#4175)

* Fix bug preventing opening of DM channels from more modal (#4181)

* 	Fixing socket timing error (#4183)

* Fixing ping/pong handler

* Fixing socket timing error

* Commenting out status broadcasting

* Removing user status changes

* Removing user status changes

* Removing user status changes

* Removing user status changes

* Adding DoPreComputeJson()

* Performance improvements (#4194)

* * Fix System Console Analytics queries
* Add db.SetConnMaxLifetime to 15 minutes
* Add "net/http/pprof" for profiling
* Add FreeOSMemory() to manually release memory on reload config

* Add flag to enable http profiler

* Fix memory leak (#4197)

* Fix memory leak

* removed unneeded nil assignment

* Fixing go routine leak (#4208)

* Merge fixes

* Merge fix

* Refactored statuses to be queried by the client rather than broadcast by the server (#4212)

* Refactored server code to reduce status broadcasts and to allow getting statuses by IDs

* Refactor client code to periodically fetch statuses

* Add store unit test for getting statuses by ids

* Fix status unit test

* Add getStatusesByIds REST API and move the client over to use that instead of the WebSocket

* Adding multiple threads to websocket hub (#4230)

* Adding multiple threads to websocket hub

* Fixing unit tests

* Fixing so websocket connections from the same user end up in the same… (#4240)

* Fixing so websocket connections from the same user end up in the same list

* Removing old comment

* Refactor user autocomplete to query the server (#4239)

* Add API for autocompleting users

* Converted at mention autocomplete to query server

* Converted user search autocomplete to query server

* Switch autocomplete API naming to use term instead of username

* Split autocomplete API into two, one for channels and for teams

* Fix copy/paste error

* Some final client scaling fixes (#4246)

* Add lazy loading of profiles to integration pages

* Add lazy loading of profiles to emoji page

* Fix JS error when receiving post in select team menu and also clean up channel store
Этот коммит содержится в:
Joram Wilander
2016-10-19 14:49:25 -04:00
коммит произвёл GitHub
родитель 0512bd26ee
Коммит 365b8b465e
129 изменённых файлов: 6411 добавлений и 2530 удалений

Просмотреть файл

@@ -1,18 +1,26 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {browserHistory} from 'react-router/es6';
import * as Utils from 'utils/utils.jsx';
import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
import TeamStore from 'stores/team_store.jsx';
import UserStore from 'stores/user_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import {loadProfilesAndTeamMembersForDMSidebar} from 'actions/user_actions.jsx';
import Client from 'client/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import * as UserAgent from 'utils/user_agent.jsx';
import Client from 'client/web_client.jsx';
import * as Utils from 'utils/utils.jsx';
import {Preferences, ActionTypes} from 'utils/constants.jsx';
import {browserHistory} from 'react-router/es6';
export function goToChannel(channel) {
if (channel.fake) {
Utils.openDirectChannelToUser(
openDirectChannelToUser(
UserStore.getProfileByUsername(channel.display_name),
() => {
browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + channel.name);
@@ -53,3 +61,124 @@ export function setChannelAsRead(channelIdParam) {
ChannelStore.emitLastViewed(Number.MAX_VALUE, false);
}
}
export function addUserToChannel(channelId, userId, success, error) {
Client.addChannelMember(
channelId,
userId,
(data) => {
UserStore.removeProfileNotInChannel(channelId, userId);
const profile = UserStore.getProfile(userId);
if (profile) {
UserStore.saveProfileInChannel(channelId, profile);
UserStore.emitInChannelChange();
}
UserStore.emitNotInChannelChange();
if (success) {
success(data);
}
},
(err) => {
AsyncClient.dispatchError(err, 'addChannelMember');
if (error) {
error(err);
}
}
);
}
export function removeUserFromChannel(channelId, userId, success, error) {
Client.removeChannelMember(
channelId,
userId,
(data) => {
UserStore.removeProfileInChannel(channelId, userId);
const profile = UserStore.getProfile(userId);
if (profile) {
UserStore.saveProfileNotInChannel(channelId, profile);
UserStore.emitNotInChannelChange();
}
UserStore.emitInChannelChange();
if (success) {
success(data);
}
},
(err) => {
AsyncClient.dispatchError(err, 'removeChannelMember');
if (error) {
error(err);
}
}
);
}
export function openDirectChannelToUser(user, success, error) {
const channelName = Utils.getDirectChannelName(UserStore.getCurrentId(), user.id);
let channel = ChannelStore.getByName(channelName);
if (channel) {
PreferenceStore.setPreference(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, user.id, 'true');
loadProfilesAndTeamMembersForDMSidebar();
AsyncClient.savePreference(
Preferences.CATEGORY_DIRECT_CHANNEL_SHOW,
user.id,
'true'
);
if (success) {
success(channel, true);
}
return;
}
channel = {
name: channelName,
last_post_at: 0,
total_msg_count: 0,
type: 'D',
display_name: user.username,
teammate_id: user.id,
status: UserStore.getStatus(user.id)
};
Client.createDirectChannel(
user.id,
(data) => {
Client.getChannel(
data.id,
(data2) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_CHANNEL,
channel: data2.channel,
member: data2.member
});
PreferenceStore.setPreference(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, user.id, 'true');
loadProfilesAndTeamMembersForDMSidebar();
AsyncClient.savePreference(
Preferences.CATEGORY_DIRECT_CHANNEL_SHOW,
user.id,
'true'
);
if (success) {
success(data2.channel, false);
}
}
);
},
() => {
browserHistory.push(TeamStore.getCurrentTeamUrl() + '/channels/' + channelName);
if (error) {
error();
}
}
);
}

46
webapp/actions/emoji_actions.jsx Обычный файл
Просмотреть файл

@@ -0,0 +1,46 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
import UserStore from 'stores/user_store.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import Client from 'client/web_client.jsx';
import {ActionTypes} from 'utils/constants.jsx';
export function loadEmoji(getProfiles = true) {
Client.listEmoji(
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_CUSTOM_EMOJIS,
emojis: data
});
if (getProfiles) {
loadProfilesForEmoji(data);
}
},
(err) => {
AsyncClient.dispatchError(err, 'listEmoji');
}
);
}
function loadProfilesForEmoji(emojiList) {
const profilesToLoad = {};
for (let i = 0; i < emojiList.length; i++) {
const emoji = emojiList[i];
if (!UserStore.hasProfile(emoji.creator_id)) {
profilesToLoad[emoji.creator_id] = true;
}
}
const list = Object.keys(profilesToLoad);
if (list.length === 0) {
return;
}
AsyncClient.getProfilesByIds(list);
}

Просмотреть файл

@@ -12,7 +12,8 @@ import TeamStore from 'stores/team_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import SearchStore from 'stores/search_store.jsx';
import {handleNewPost} from 'actions/post_actions.jsx';
import {handleNewPost, loadPosts, loadPostsBefore, loadPostsAfter} from 'actions/post_actions.jsx';
import {loadProfilesAndTeamMembersForDMSidebar} from 'actions/user_actions.jsx';
import Constants from 'utils/constants.jsx';
const ActionTypes = Constants.ActionTypes;
@@ -43,9 +44,9 @@ export function emitChannelClickEvent(channel) {
function switchToChannel(chan) {
AsyncClient.getChannels(true);
AsyncClient.getMoreChannels(true);
AsyncClient.getChannelExtraInfo(chan.id);
AsyncClient.getChannelStats(chan.id);
AsyncClient.updateLastViewedAt(chan.id);
AsyncClient.getPosts(chan.id);
loadPosts(chan.id);
trackPage();
AppDispatcher.handleViewAction({
@@ -108,7 +109,7 @@ export function emitInitialLoad(callback) {
if (data.team_members) {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_TEAM_MEMBERS,
type: ActionTypes.RECEIVED_MY_TEAM_MEMBERS,
team_members: data.team_members
});
}
@@ -143,9 +144,9 @@ export function doFocusPost(channelId, postId, data) {
});
AsyncClient.getChannels(true);
AsyncClient.getMoreChannels(true);
AsyncClient.getChannelExtraInfo(channelId);
AsyncClient.getPostsBefore(postId, 0, Constants.POST_FOCUS_CONTEXT_RADIUS, true);
AsyncClient.getPostsAfter(postId, 0, Constants.POST_FOCUS_CONTEXT_RADIUS, true);
AsyncClient.getChannelStats(channelId);
loadPostsBefore(postId, 0, Constants.POST_FOCUS_CONTEXT_RADIUS, true);
loadPostsAfter(postId, 0, Constants.POST_FOCUS_CONTEXT_RADIUS, true);
}
export function emitPostFocusEvent(postId, onSuccess) {
@@ -246,14 +247,14 @@ export function emitLoadMorePostsFocusedTopEvent() {
export function loadMorePostsTop(id, isFocusPost) {
const earliestPostId = PostStore.getEarliestPost(id).id;
if (PostStore.requestVisibilityIncrease(id, Constants.POST_CHUNK_SIZE)) {
AsyncClient.getPostsBefore(earliestPostId, 0, Constants.POST_CHUNK_SIZE, isFocusPost);
loadPostsBefore(earliestPostId, 0, Constants.POST_CHUNK_SIZE, isFocusPost);
}
}
export function emitLoadMorePostsFocusedBottomEvent() {
const id = PostStore.getFocusedPostId();
const latestPostId = PostStore.getLatestPost(id).id;
AsyncClient.getPostsAfter(latestPostId, 0, Constants.POST_CHUNK_SIZE, Boolean(id));
loadPostsAfter(latestPostId, 0, Constants.POST_CHUNK_SIZE, Boolean(id));
}
export function emitUserPostedEvent(post) {
@@ -362,7 +363,7 @@ export function emitClearSuggestions(suggestionId) {
export function emitPreferenceChangedEvent(preference) {
if (preference.category === Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW) {
AsyncClient.getDirectProfiles();
loadProfilesAndTeamMembersForDMSidebar();
}
AppDispatcher.handleServerAction({
@@ -437,7 +438,7 @@ export function loadDefaultLocale() {
export function viewLoggedIn() {
AsyncClient.getChannels();
AsyncClient.getMoreChannels();
AsyncClient.getChannelExtraInfo();
AsyncClient.getChannelStats();
// Clear pending posts (shouldn't have pending posts if we are loading)
PostStore.clearPendingPosts();

114
webapp/actions/integration_actions.jsx Обычный файл
Просмотреть файл

@@ -0,0 +1,114 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
import TeamStore from 'stores/team_store.jsx';
import UserStore from 'stores/user_store.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import Client from 'client/web_client.jsx';
import {ActionTypes} from 'utils/constants.jsx';
export function loadIncomingHooks() {
Client.listIncomingHooks(
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_INCOMING_WEBHOOKS,
teamId: TeamStore.getCurrentId(),
incomingWebhooks: data
});
loadProfilesForIncomingHooks(data);
},
(err) => {
AsyncClient.dispatchError(err, 'listIncomingHooks');
}
);
}
function loadProfilesForIncomingHooks(hooks) {
const profilesToLoad = {};
for (let i = 0; i < hooks.length; i++) {
const hook = hooks[i];
if (!UserStore.hasProfile(hook.user_id)) {
profilesToLoad[hook.user_id] = true;
}
}
const list = Object.keys(profilesToLoad);
if (list.length === 0) {
return;
}
AsyncClient.getProfilesByIds(list);
}
export function loadOutgoingHooks() {
Client.listOutgoingHooks(
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_OUTGOING_WEBHOOKS,
teamId: TeamStore.getCurrentId(),
outgoingWebhooks: data
});
loadProfilesForOutgoingHooks(data);
},
(err) => {
AsyncClient.dispatchError(err, 'listOutgoingHooks');
}
);
}
function loadProfilesForOutgoingHooks(hooks) {
const profilesToLoad = {};
for (let i = 0; i < hooks.length; i++) {
const hook = hooks[i];
if (!UserStore.hasProfile(hook.creator_id)) {
profilesToLoad[hook.creator_id] = true;
}
}
const list = Object.keys(profilesToLoad);
if (list.length === 0) {
return;
}
AsyncClient.getProfilesByIds(list);
}
export function loadTeamCommands() {
Client.listTeamCommands(
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_COMMANDS,
teamId: Client.teamId,
commands: data
});
loadProfilesForCommands(data);
},
(err) => {
AsyncClient.dispatchError(err, 'loadTeamCommands');
}
);
}
function loadProfilesForCommands(commands) {
const profilesToLoad = {};
for (let i = 0; i < commands.length; i++) {
const command = commands[i];
if (!UserStore.hasProfile(command.creator_id)) {
profilesToLoad[command.creator_id] = true;
}
}
const list = Object.keys(profilesToLoad);
if (list.length === 0) {
return;
}
AsyncClient.getProfilesByIds(list);
}

Просмотреть файл

@@ -8,6 +8,8 @@ import PostStore from 'stores/post_store.jsx';
import TeamStore from 'stores/team_store.jsx';
import UserStore from 'stores/user_store.jsx';
import {loadStatusesForChannel} from 'actions/status_actions.jsx';
import * as PostUtils from 'utils/post_utils.jsx';
import Client from 'client/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
@@ -52,6 +54,8 @@ export function handleNewPost(post, msg) {
post,
websocketMessageProps
});
loadProfilesForPosts(data.posts);
},
(err) => {
AsyncClient.dispatchError(err, 'getPost');
@@ -115,7 +119,7 @@ export function setUnreadPost(channelId, postId) {
member.last_viewed_at = lastViewed;
member.msg_count = channel.total_msg_count - unreadPosts;
member.mention_count = 0;
ChannelStore.setChannelMember(member);
ChannelStore.storeMyChannelMember(member);
ChannelStore.setUnreadCount(channelId);
AsyncClient.setLastViewedAt(lastViewed, channelId);
}
@@ -153,9 +157,156 @@ export function getFlaggedPosts() {
results: data,
is_flagged_posts: true
});
loadProfilesForPosts(data.posts);
},
(err) => {
AsyncClient.dispatchError(err, 'getFlaggedPosts');
}
);
}
export function loadPosts(channelId = ChannelStore.getCurrentId()) {
const postList = PostStore.getAllPosts(channelId);
const latestPostTime = PostStore.getLatestPostFromPageTime(channelId);
if (!postList || Object.keys(postList).length === 0 || postList.order.length < Constants.POST_CHUNK_SIZE || latestPostTime === 0) {
loadPostsPage(channelId, Constants.POST_CHUNK_SIZE);
return;
}
Client.getPosts(
channelId,
latestPostTime,
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_POSTS,
id: channelId,
before: true,
numRequested: 0,
post_list: data
});
loadProfilesForPosts(data.posts);
loadStatusesForChannel(channelId);
},
(err) => {
AsyncClient.dispatchError(err, 'loadPosts');
}
);
}
export function loadPostsPage(channelId = ChannelStore.getCurrentId(), max = Constants.POST_CHUNK_SIZE) {
const postList = PostStore.getAllPosts(channelId);
// if we already have more than POST_CHUNK_SIZE posts,
// let's get the amount we have but rounded up to next multiple of POST_CHUNK_SIZE,
// with a max
let numPosts = Math.min(max, Constants.POST_CHUNK_SIZE);
if (postList && postList.order.length > 0) {
numPosts = Math.min(max, Constants.POST_CHUNK_SIZE * Math.ceil(postList.order.length / Constants.POST_CHUNK_SIZE));
}
Client.getPostsPage(
channelId,
0,
numPosts,
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_POSTS,
id: channelId,
before: true,
numRequested: numPosts,
checkLatest: true,
post_list: data
});
loadProfilesForPosts(data.posts);
loadStatusesForChannel(channelId);
},
(err) => {
AsyncClient.dispatchError(err, 'loadPostsPage');
}
);
}
export function loadPostsBefore(postId, offset, numPost, isPost) {
const channelId = ChannelStore.getCurrentId();
if (channelId == null) {
return;
}
Client.getPostsBefore(
channelId,
postId,
offset,
numPost,
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_POSTS,
id: channelId,
before: true,
numRequested: numPost,
post_list: data,
isPost
});
loadProfilesForPosts(data.posts);
loadStatusesForChannel(channelId);
},
(err) => {
AsyncClient.dispatchError(err, 'loadPostsBefore');
}
);
}
export function loadPostsAfter(postId, offset, numPost, isPost) {
const channelId = ChannelStore.getCurrentId();
if (channelId == null) {
return;
}
Client.getPostsAfter(
channelId,
postId,
offset,
numPost,
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_POSTS,
id: channelId,
before: false,
numRequested: numPost,
post_list: data,
isPost
});
loadProfilesForPosts(data.posts);
loadStatusesForChannel(channelId);
},
(err) => {
AsyncClient.dispatchError(err, 'loadPostsAfter');
}
);
}
function loadProfilesForPosts(posts) {
const profilesToLoad = {};
for (const pid in posts) {
if (!posts.hasOwnProperty(pid)) {
continue;
}
const post = posts[pid];
if (!UserStore.hasProfile(post.user_id)) {
profilesToLoad[post.user_id] = true;
}
}
const list = Object.keys(profilesToLoad);
if (list.length === 0) {
return;
}
AsyncClient.getProfilesByIds(list);
}

133
webapp/actions/status_actions.jsx Обычный файл
Просмотреть файл

@@ -0,0 +1,133 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import PostStore from 'stores/post_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import Client from 'client/web_client.jsx';
import {ActionTypes, Preferences, Constants} from 'utils/constants.jsx';
export function loadStatusesForChannel(channelId = ChannelStore.getCurrentId()) {
const postList = PostStore.getVisiblePosts(channelId);
if (!postList || !postList.posts) {
return;
}
const statusesToLoad = {};
for (const pid in postList.posts) {
if (!postList.posts.hasOwnProperty(pid)) {
continue;
}
const post = postList.posts[pid];
statusesToLoad[post.user_id] = true;
}
loadStatusesByIds(Object.keys(statusesToLoad));
}
export function loadStatusesForDMSidebar() {
const dmPrefs = PreferenceStore.getCategory(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW);
const statusesToLoad = [];
for (const [key, value] of dmPrefs) {
if (value === 'true') {
statusesToLoad.push(key);
}
}
loadStatusesByIds(statusesToLoad);
}
export function loadStatusesForChannelAndSidebar() {
const statusesToLoad = {};
const channelId = ChannelStore.getCurrentId();
const postList = PostStore.getVisiblePosts(channelId);
if (postList && postList.posts) {
for (const pid in postList.posts) {
if (!postList.posts.hasOwnProperty(pid)) {
continue;
}
const post = postList.posts[pid];
statusesToLoad[post.user_id] = true;
}
}
const dmPrefs = PreferenceStore.getCategory(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW);
for (const [key, value] of dmPrefs) {
if (value === 'true') {
statusesToLoad[key] = true;
}
}
loadStatusesByIds(Object.keys(statusesToLoad));
}
export function loadStatusesForProfilesList(users) {
if (users == null) {
return;
}
const statusesToLoad = [];
for (let i = 0; i < users.length; i++) {
statusesToLoad.push(users[i].id);
}
loadStatusesByIds(statusesToLoad);
}
export function loadStatusesForProfilesMap(users) {
if (users == null) {
return;
}
const statusesToLoad = [];
for (const userId in users) {
if (!users.hasOwnProperty(userId)) {
return;
}
statusesToLoad.push(userId);
}
loadStatusesByIds(statusesToLoad);
}
export function loadStatusesByIds(userIds) {
if (userIds.length === 0) {
return;
}
Client.getStatusesByIds(
userIds,
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_STATUSES,
statuses: data
});
}
);
}
let intervalId = '';
export function startPeriodicStatusUpdates() {
clearInterval(intervalId);
intervalId = setInterval(
() => {
loadStatusesForChannelAndSidebar();
},
Constants.STATUS_INTERVAL
);
}
export function stopPeriodicStatusUpdates() {
clearInterval(intervalId);
}

Просмотреть файл

@@ -2,6 +2,7 @@
// See License.txt for license information.
import UserStore from 'stores/user_store.jsx';
import TeamStore from 'stores/team_store.jsx';
import Constants from 'utils/constants.jsx';
const ActionTypes = Constants.ActionTypes;
@@ -19,8 +20,6 @@ export function checkIfTeamExists(teamName, onSuccess, onError) {
export function createTeam(team, onSuccess, onError) {
Client.createTeam(team,
(rteam) => {
AsyncClient.getDirectProfiles();
AppDispatcher.handleServerAction({
type: ActionTypes.CREATED_TEAM,
team: rteam,
@@ -36,3 +35,25 @@ export function createTeam(team, onSuccess, onError) {
onError
);
}
export function removeUserFromTeam(teamId, userId, success, error) {
Client.removeUserFromTeam(
teamId,
userId,
() => {
TeamStore.removeMemberInTeam(teamId, userId);
AsyncClient.getUser(userId);
if (success) {
success();
}
},
(err) => {
AsyncClient.dispatchError(err, 'removeUserFromTeam');
if (error) {
error(err);
}
}
);
}

Просмотреть файл

@@ -2,12 +2,17 @@
// See License.txt for license information.
import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import Client from 'client/web_client.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import TeamStore from 'stores/team_store.jsx';
import UserStore from 'stores/user_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import {loadStatusesForProfilesList, loadStatusesForProfilesMap} from 'actions/status_actions.jsx';
import {getDirectChannelName} from 'utils/utils.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import Client from 'client/web_client.jsx';
import {ActionTypes, Preferences} from 'utils/constants.jsx';
@@ -29,9 +34,179 @@ export function switchFromLdapToEmail(email, password, ldapPassword, onSuccess,
);
}
export function getMoreDmList() {
AsyncClient.getTeamMembers(TeamStore.getCurrentId());
AsyncClient.getProfilesForDirectMessageList();
export function loadProfilesAndTeamMembers(offset, limit, teamId = TeamStore.getCurrentId(), success, error) {
Client.getProfilesInTeam(
teamId,
offset,
limit,
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_PROFILES_IN_TEAM,
profiles: data,
team_id: teamId,
offset,
count: Object.keys(data).length
});
loadTeamMembersForProfilesMap(data, teamId, success, error);
loadStatusesForProfilesMap(data);
},
(err) => {
AsyncClient.dispatchError(err, 'getProfilesInTeam');
}
);
}
export function loadTeamMembersForProfilesMap(profiles, teamId = TeamStore.getCurrentId(), success, error) {
const membersToLoad = {};
for (const pid in profiles) {
if (!profiles.hasOwnProperty(pid)) {
continue;
}
if (!TeamStore.hasActiveMemberInTeam(teamId, pid)) {
membersToLoad[pid] = true;
}
}
const list = Object.keys(membersToLoad);
if (list.length === 0) {
if (success) {
success({});
}
return;
}
loadTeamMembersForProfiles(list, teamId, success, error);
}
export function loadTeamMembersForProfilesList(profiles, teamId = TeamStore.getCurrentId(), success, error) {
const membersToLoad = {};
for (let i = 0; i < profiles.length; i++) {
const pid = profiles[i].id;
if (!TeamStore.hasActiveMemberInTeam(teamId, pid)) {
membersToLoad[pid] = true;
}
}
const list = Object.keys(membersToLoad);
if (list.length === 0) {
if (success) {
success({});
}
return;
}
loadTeamMembersForProfiles(list, teamId, success, error);
}
function loadTeamMembersForProfiles(userIds, teamId, success, error) {
Client.getTeamMembersByIds(
teamId,
userIds,
(data) => {
const memberMap = {};
for (let i = 0; i < data.length; i++) {
memberMap[data[i].user_id] = data[i];
}
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_MEMBERS_IN_TEAM,
team_id: teamId,
team_members: memberMap
});
if (success) {
success(data);
}
},
(err) => {
AsyncClient.dispatchError(err, 'getTeamMembersByIds');
if (error) {
error(err);
}
}
);
}
function populateDMChannelsWithProfiles(userIds) {
const currentUserId = UserStore.getCurrentId();
for (let i = 0; i < userIds.length; i++) {
const channelName = getDirectChannelName(currentUserId, userIds[i]);
const channel = ChannelStore.getByName(channelName);
if (channel) {
UserStore.saveUserIdInChannel(channel.id, userIds[i]);
}
}
}
export function loadProfilesAndTeamMembersForDMSidebar() {
const dmPrefs = PreferenceStore.getCategory(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW);
const teamId = TeamStore.getCurrentId();
const profilesToLoad = [];
const membersToLoad = [];
for (const [key, value] of dmPrefs) {
if (value === 'true') {
if (!UserStore.hasProfile(key)) {
profilesToLoad.push(key);
}
membersToLoad.push(key);
}
}
if (profilesToLoad.length > 0) {
Client.getProfilesByIds(
profilesToLoad,
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_PROFILES,
profiles: data
});
// Use membersToLoad so we get all the DM profiles even if they were already loaded
populateDMChannelsWithProfiles(membersToLoad);
},
(err) => {
AsyncClient.dispatchError(err, 'getProfilesByIds');
}
);
} else {
populateDMChannelsWithProfiles(membersToLoad);
}
if (membersToLoad.length > 0) {
Client.getTeamMembersByIds(
teamId,
membersToLoad,
(data) => {
const memberMap = {};
for (let i = 0; i < data.length; i++) {
memberMap[data[i].user_id] = data[i];
}
const nonMembersMap = {};
for (let i = 0; i < membersToLoad.length; i++) {
if (!memberMap[membersToLoad[i]]) {
nonMembersMap[membersToLoad[i]] = true;
}
}
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_MEMBERS_IN_TEAM,
team_id: teamId,
team_members: memberMap,
non_team_members: nonMembersMap
});
},
(err) => {
AsyncClient.dispatchError(err, 'getTeamMembersByIds');
}
);
}
}
export function saveTheme(teamId, theme, onSuccess, onError) {
@@ -82,3 +257,62 @@ function onThemeSaved(teamId, theme, onSuccess) {
onSuccess();
}
export function searchUsers(term, teamId = TeamStore.getCurrentId(), options = {}, success, error) {
Client.searchUsers(
term,
teamId,
options,
(data) => {
loadStatusesForProfilesList(data);
if (success) {
success(data);
}
},
(err) => {
AsyncClient.dispatchError(err, 'searchUsers');
if (error) {
error(err);
}
}
);
}
export function autocompleteUsersInChannel(username, channelId, success, error) {
Client.autocompleteUsersInChannel(
username,
channelId,
(data) => {
if (success) {
success(data);
}
},
(err) => {
AsyncClient.dispatchError(err, 'autocompleteUsersInChannel');
if (error) {
error(err);
}
}
);
}
export function autocompleteUsersInTeam(username, success, error) {
Client.autocompleteUsersInTeam(
username,
(data) => {
if (success) {
success(data);
}
},
(err) => {
AsyncClient.dispatchError(err, 'autocompleteUsersInTeam');
if (error) {
error(err);
}
}
);
}

Просмотреть файл

@@ -3,8 +3,6 @@
import $ from 'jquery';
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
import UserStore from 'stores/user_store.jsx';
import TeamStore from 'stores/team_store.jsx';
import PostStore from 'stores/post_store.jsx';
@@ -20,10 +18,11 @@ import * as Utils from 'utils/utils.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
import * as UserActions from 'actions/user_actions.jsx';
import {handleNewPost} from 'actions/post_actions.jsx';
import {handleNewPost, loadPosts} from 'actions/post_actions.jsx';
import {loadProfilesAndTeamMembersForDMSidebar} from 'actions/user_actions.jsx';
import * as StatusActions from 'actions/status_actions.jsx';
import {Constants, SocketEvents, ActionTypes} from 'utils/constants.jsx';
import {Constants, SocketEvents, UserStatuses} from 'utils/constants.jsx';
import {browserHistory} from 'react-router/es6';
@@ -53,6 +52,7 @@ export function initialize() {
connUrl += Client.getUsersRoute() + '/websocket';
WebSocketClient.setEventCallback(handleEvent);
WebSocketClient.setFirstConnectCallback(handleFirstConnect);
WebSocketClient.setReconnectCallback(handleReconnect);
WebSocketClient.setCloseCallback(handleClose);
WebSocketClient.initialize(connUrl);
@@ -64,22 +64,19 @@ export function close() {
}
export function getStatuses() {
WebSocketClient.getStatuses(
(resp) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_STATUSES,
statuses: resp.data
});
}
);
StatusActions.loadStatusesForChannelAndSidebar();
}
function handleFirstConnect() {
getStatuses();
ErrorStore.clearLastError();
ErrorStore.emitChange();
}
function handleReconnect() {
if (Client.teamId) {
AsyncClient.getChannels();
AsyncClient.getPosts(ChannelStore.getCurrentId());
AsyncClient.getTeamMembers(TeamStore.getCurrentId());
AsyncClient.getProfiles();
loadPosts(ChannelStore.getCurrentId());
}
getStatuses();
@@ -112,7 +109,7 @@ function handleEvent(msg) {
break;
case SocketEvents.NEW_USER:
handleNewUserEvent();
handleNewUserEvent(msg);
break;
case SocketEvents.LEAVE_TEAM:
@@ -170,6 +167,10 @@ function handleEvent(msg) {
function handleNewPostEvent(msg) {
const post = JSON.parse(msg.data.post);
handleNewPost(post, msg);
if (UserStore.getStatus(post.user_id) !== UserStatuses.ONLINE) {
StatusActions.loadStatusesByIds([post.user_id]);
}
}
function handlePostEditEvent(msg) {
@@ -196,36 +197,33 @@ function handlePostDeleteEvent(msg) {
}
}
function handleNewUserEvent() {
AsyncClient.getTeamMembers(TeamStore.getCurrentId());
AsyncClient.getProfiles();
AsyncClient.getDirectProfiles();
AsyncClient.getChannelExtraInfo();
function handleNewUserEvent(msg) {
AsyncClient.getUser(msg.user_id);
AsyncClient.getChannelStats();
loadProfilesAndTeamMembersForDMSidebar();
}
function handleLeaveTeamEvent(msg) {
if (UserStore.getCurrentId() === msg.data.user_id) {
TeamStore.removeTeamMember(msg.broadcast.team_id);
TeamStore.removeMyTeamMember(msg.broadcast.team_id);
// if the are on the team begin removed redirect them to the root
// if they are on the team being removed redirect them to the root
if (TeamStore.getCurrentId() === msg.broadcast.team_id) {
TeamStore.setCurrentId('');
Client.setTeamId('');
browserHistory.push('/');
}
} else if (TeamStore.getCurrentId() === msg.broadcast.team_id) {
UserActions.getMoreDmList();
}
}
function handleDirectAddedEvent(msg) {
AsyncClient.getChannel(msg.broadcast.channel_id);
AsyncClient.getDirectProfiles();
loadProfilesAndTeamMembersForDMSidebar();
}
function handleUserAddedEvent(msg) {
if (ChannelStore.getCurrentId() === msg.broadcast.channel_id) {
AsyncClient.getChannelExtraInfo();
AsyncClient.getChannelStats();
}
if (TeamStore.getCurrentId() === msg.data.team_id && UserStore.getCurrentId() === msg.data.user_id) {
@@ -248,7 +246,7 @@ function handleUserRemovedEvent(msg) {
$('#removed_from_channel').modal('show');
}
} else if (ChannelStore.getCurrentId() === msg.broadcast.channel_id) {
AsyncClient.getChannelExtraInfo();
AsyncClient.getChannelStats();
}
}
@@ -287,6 +285,10 @@ function handlePreferenceChangedEvent(msg) {
function handleUserTypingEvent(msg) {
GlobalActions.emitRemoteUserTypingEvent(msg.broadcast.channel_id, msg.data.user_id, msg.data.parent_id);
if (UserStore.getStatus(msg.data.user_id) !== UserStatuses.ONLINE) {
StatusActions.loadStatusesByIds([msg.data.user_id]);
}
}
function handleStatusChangedEvent(msg) {
@@ -301,4 +303,4 @@ function handleHelloEvent(msg) {
function handleWebrtc(msg) {
const data = msg.data;
return WebrtcActions.handle(data);
}
}