Multiple fixes to major performance issues with teams with 50+ users

Этот коммит содержится в:
JoramWilander
2015-11-03 14:52:25 -05:00
родитель fc9aefb868
Коммит fb5b57836e
7 изменённых файлов: 82 добавлений и 77 удалений

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

@@ -49,7 +49,7 @@ func InitUser(r *mux.Router) {
sr.Handle("/newimage", ApiUserRequired(uploadProfileImage)).Methods("POST") sr.Handle("/newimage", ApiUserRequired(uploadProfileImage)).Methods("POST")
sr.Handle("/me", ApiAppHandler(getMe)).Methods("GET") sr.Handle("/me", ApiAppHandler(getMe)).Methods("GET")
sr.Handle("/status", ApiUserRequiredActivity(getStatuses, false)).Methods("GET") sr.Handle("/status", ApiUserRequiredActivity(getStatuses, false)).Methods("POST")
sr.Handle("/profiles", ApiUserRequired(getProfiles)).Methods("GET") sr.Handle("/profiles", ApiUserRequired(getProfiles)).Methods("GET")
sr.Handle("/profiles/{id:[A-Za-z0-9]+}", ApiUserRequired(getProfiles)).Methods("GET") sr.Handle("/profiles/{id:[A-Za-z0-9]+}", ApiUserRequired(getProfiles)).Methods("GET")
sr.Handle("/{id:[A-Za-z0-9]+}", ApiUserRequired(getUser)).Methods("GET") sr.Handle("/{id:[A-Za-z0-9]+}", ApiUserRequired(getUser)).Methods("GET")
@@ -1483,16 +1483,31 @@ func updateUserNotify(c *Context, w http.ResponseWriter, r *http.Request) {
} }
func getStatuses(c *Context, w http.ResponseWriter, r *http.Request) { func getStatuses(c *Context, w http.ResponseWriter, r *http.Request) {
userIds := model.ArrayFromJson(r.Body)
if len(userIds) == 0 {
c.SetInvalidParam("getStatuses", "userIds")
return
}
if result := <-Srv.Store.User().GetProfiles(c.Session.TeamId); result.Err != nil { if result := <-Srv.Store.User().GetProfiles(c.Session.TeamId); result.Err != nil {
c.Err = result.Err c.Err = result.Err
return return
} else { } else {
profiles := result.Data.(map[string]*model.User) profiles := result.Data.(map[string]*model.User)
statuses := map[string]string{} statuses := map[string]string{}
for _, profile := range profiles { for _, profile := range profiles {
found := false
for _, uid := range userIds {
if uid == profile.Id {
found = true
}
}
if !found {
continue
}
if profile.IsOffline() { if profile.IsOffline() {
statuses[profile.Id] = model.USER_OFFLINE statuses[profile.Id] = model.USER_OFFLINE
} else if profile.IsAway() { } else if profile.IsAway() {

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

@@ -38,7 +38,7 @@ export default class ChannelLoader extends React.Component {
/* Set up interval functions */ /* Set up interval functions */
this.intervalId = setInterval( this.intervalId = setInterval(
function pollStatuses() { () => {
AsyncClient.getStatuses(); AsyncClient.getStatuses();
}, },
30000 30000

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

@@ -100,74 +100,53 @@ export default class Sidebar extends React.Component {
} }
getStateFromStores() { getStateFromStores() {
const members = ChannelStore.getAllMembers(); const members = ChannelStore.getAllMembers();
var teamMemberMap = UserStore.getActiveOnlyProfiles(); const currentChannelId = ChannelStore.getCurrentId();
var currentId = ChannelStore.getCurrentId();
const currentUserId = UserStore.getCurrentId();
var teammates = []; const channels = Object.assign([], ChannelStore.getAll());
for (var id in teamMemberMap) { const publicChannels = channels.filter((channel) => channel.type === Constants.OPEN_CHANNEL);
if (id === currentUserId) { const privateChannels = channels.filter((channel) => channel.type === Constants.PRIVATE_CHANNEL);
continue; const directChannels = channels.filter((channel) => channel.type === Constants.DM_CHANNEL);
}
teammates.push(teamMemberMap[id]);
}
const preferences = PreferenceStore.getPreferences(Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW); const preferences = PreferenceStore.getPreferences(Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW);
var visibleDirectChannels = []; var visibleDirectChannels = [];
var hiddenDirectChannelCount = 0; for (var i = 0; i < directChannels.length; i++) {
for (var i = 0; i < teammates.length; i++) { const dm = directChannels[i];
const teammate = teammates[i]; const teammate = Utils.getDirectTeammate(dm.id);
if (teammate.id === currentUserId) { const member = members[dm.id];
continue; const msgCount = dm.total_msg_count - member.msg_count;
}
const channelName = Utils.getDirectChannelName(currentUserId, teammate.id); // always show a channel if either it is the current one or if it is unread, but it is not currently being left
const forceShow = (currentChannelId === dm.id || msgCount > 0) && !this.isLeaving.get(dm.id);
const preferenceShow = preferences.some((preference) => (preference.name === teammate.id && preference.value !== 'false'));
let forceShow = false; if (preferenceShow || forceShow) {
let channel = ChannelStore.getByName(channelName); dm.display_name = Utils.displayUsername(teammate.id);
dm.teammate_id = teammate.id;
dm.status = UserStore.getStatus(teammate.id);
if (channel) { visibleDirectChannels.push(dm);
const member = members[channel.id];
const msgCount = channel.total_msg_count - member.msg_count;
// always show a channel if either it is the current one or if it is unread, but it is not currently being left if (forceShow && !preferenceShow) {
forceShow = (currentId === channel.id || msgCount > 0) && !this.isLeaving.get(channel.id); // make sure that unread direct channels are visible
} else { const preference = PreferenceStore.setPreference(Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, teammate.id, 'true');
channel = {}; AsyncClient.savePreferences([preference]);
channel.fake = true; }
channel.name = channelName;
channel.last_post_at = 0;
channel.total_msg_count = 0;
channel.type = 'D';
}
channel.display_name = Utils.displayUsername(teammate.id);
channel.teammate_id = teammate.id;
channel.status = UserStore.getStatus(teammate.id);
if (preferences.some((preference) => (preference.name === teammate.id && preference.value !== 'false'))) {
visibleDirectChannels.push(channel);
} else if (forceShow) {
// make sure that unread direct channels are visible
const preference = PreferenceStore.setPreference(Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, teammate.id, 'true');
AsyncClient.savePreferences([preference]);
visibleDirectChannels.push(channel);
} else {
hiddenDirectChannelCount += 1;
} }
} }
const hiddenDirectChannelCount = UserStore.getActiveOnlyProfileList().length - visibleDirectChannels.length;
visibleDirectChannels.sort(this.sortChannelsByDisplayName); visibleDirectChannels.sort(this.sortChannelsByDisplayName);
const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'}); const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'});
return { return {
activeId: currentId, activeId: currentChannelId,
channels: ChannelStore.getAll(),
members, members,
publicChannels,
privateChannels,
visibleDirectChannels, visibleDirectChannels,
hiddenDirectChannelCount, hiddenDirectChannelCount,
showTutorialTip: parseInt(tutorialPref.value, 10) === TutorialSteps.CHANNEL_POPOVER showTutorialTip: parseInt(tutorialPref.value, 10) === TutorialSteps.CHANNEL_POPOVER
@@ -534,11 +513,9 @@ export default class Sidebar extends React.Component {
this.lastUnreadChannel = null; this.lastUnreadChannel = null;
// create elements for all 3 types of channels // create elements for all 3 types of channels
const publicChannels = this.state.channels.filter((channel) => channel.type === 'O'); const publicChannelItems = this.state.publicChannels.map(this.createChannelElement);
const publicChannelItems = publicChannels.map(this.createChannelElement);
const privateChannels = this.state.channels.filter((channel) => channel.type === 'P'); const privateChannelItems = this.state.privateChannels.map(this.createChannelElement);
const privateChannelItems = privateChannels.map(this.createChannelElement);
const directMessageItems = this.state.visibleDirectChannels.map((channel, index, arr) => { const directMessageItems = this.state.visibleDirectChannels.map((channel, index, arr) => {
return this.createChannelElement(channel, index, arr, this.handleLeaveDirectChannel); return this.createChannelElement(channel, index, arr, this.handleLeaveDirectChannel);

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

@@ -204,12 +204,13 @@ class UserStoreClass extends EventEmitter {
} }
getActiveOnlyProfiles() { getActiveOnlyProfiles() {
var active = {}; const active = {};
var current = this.getProfiles(); const profiles = this.getProfiles();
const currentId = this.getCurrentId();
for (var key in current) { for (var key in profiles) {
if (current[key].delete_at === 0) { if (profiles[key].delete_at === 0 && profiles[key].id !== currentId) {
active[key] = current[key]; active[key] = profiles[key];
} }
} }
@@ -219,9 +220,10 @@ class UserStoreClass extends EventEmitter {
getActiveOnlyProfileList() { getActiveOnlyProfileList() {
const profileMap = this.getActiveOnlyProfiles(); const profileMap = this.getActiveOnlyProfiles();
const profiles = []; const profiles = [];
const currentId = this.getCurrentId();
for (const id in profileMap) { for (const id in profileMap) {
if (profileMap.hasOwnProperty(id)) { if (profileMap.hasOwnProperty(id) && id !== currentId) {
profiles.push(profileMap[id]); profiles.push(profileMap[id]);
} }
} }
@@ -235,6 +237,14 @@ class UserStoreClass extends EventEmitter {
BrowserStore.setItem('profiles', ps); BrowserStore.setItem('profiles', ps);
} }
saveProfiles(profiles) {
const currentId = this.getCurrentId();
if (currentId in profiles) {
delete profiles[currentId];
}
BrowserStore.setItem('profiles', profiles);
}
setSessions(sessions) { setSessions(sessions) {
BrowserStore.setItem('sessions', sessions); BrowserStore.setItem('sessions', sessions);
} }
@@ -320,15 +330,8 @@ UserStore.dispatchToken = AppDispatcher.register((payload) => {
switch (action.type) { switch (action.type) {
case ActionTypes.RECIEVED_PROFILES: case ActionTypes.RECIEVED_PROFILES:
for (var id in action.profiles) { UserStore.saveProfiles(action.profiles);
// profiles can have incomplete data, so don't overwrite current user UserStore.emitChange();
if (id === UserStore.getCurrentId()) {
continue;
}
var profile = action.profiles[id];
UserStore.saveProfile(profile);
UserStore.emitChange(profile.id);
}
break; break;
case ActionTypes.RECIEVED_ME: case ActionTypes.RECIEVED_ME:
UserStore.setCurrentUser(action.me); UserStore.setCurrentUser(action.me);

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

@@ -588,13 +588,21 @@ export function getMe() {
} }
export function getStatuses() { export function getStatuses() {
if (isCallInProgress('getStatuses')) { const directChannels = ChannelStore.getAll().filter((channel) => channel.type === Constants.DM_CHANNEL);
const teammateIds = [];
for (var i = 0; i < directChannels.length; i++) {
const teammate = utils.getDirectTeammate(directChannels[i].id);
teammateIds.push(teammate.id);
}
if (isCallInProgress('getStatuses') || teammateIds.length === 0) {
return; return;
} }
callTracker.getStatuses = utils.getTimestamp(); callTracker.getStatuses = utils.getTimestamp();
client.getStatuses( client.getStatuses(teammateIds,
function getStatusesSuccess(data, textStatus, xhr) { (data, textStatus, xhr) => {
callTracker.getStatuses = 0; callTracker.getStatuses = 0;
if (xhr.status === 304 || !data) { if (xhr.status === 304 || !data) {
@@ -606,7 +614,7 @@ export function getStatuses() {
statuses: data statuses: data
}); });
}, },
function getStatusesFailure(err) { (err) => {
callTracker.getStatuses = 0; callTracker.getStatuses = 0;
dispatchError(err, 'getStatuses'); dispatchError(err, 'getStatuses');
} }

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

@@ -1069,12 +1069,13 @@ export function exportTeam(success, error) {
}); });
} }
export function getStatuses(success, error) { export function getStatuses(ids, success, error) {
$.ajax({ $.ajax({
url: '/api/v1/users/status', url: '/api/v1/users/status',
dataType: 'json', dataType: 'json',
contentType: 'application/json', contentType: 'application/json',
type: 'GET', type: 'POST',
data: JSON.stringify(ids),
success, success,
error: function onError(xhr, status, err) { error: function onError(xhr, status, err) {
var e = handleError('getStatuses', xhr, status, err); var e = handleError('getStatuses', xhr, status, err);

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

@@ -127,6 +127,7 @@ module.exports = {
MAX_DMS: 20, MAX_DMS: 20,
DM_CHANNEL: 'D', DM_CHANNEL: 'D',
OPEN_CHANNEL: 'O', OPEN_CHANNEL: 'O',
PRIVATE_CHANNEL: 'P',
INVITE_TEAM: 'I', INVITE_TEAM: 'I',
OPEN_TEAM: 'O', OPEN_TEAM: 'O',
MAX_POST_LEN: 4000, MAX_POST_LEN: 4000,