Merge pull request #1289 from mattermost/plt-516-2
PLT-516 Part 1 of performance fixes for large teams
Этот коммит содержится в:
19
api/user.go
19
api/user.go
@@ -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() {
|
||||||
|
|||||||
@@ -1020,9 +1020,15 @@ func TestStatuses(t *testing.T) {
|
|||||||
ruser := Client.Must(Client.CreateUser(&user, "")).Data.(*model.User)
|
ruser := Client.Must(Client.CreateUser(&user, "")).Data.(*model.User)
|
||||||
store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
|
store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
|
||||||
|
|
||||||
|
user2 := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
|
||||||
|
ruser2 := Client.Must(Client.CreateUser(&user2, "")).Data.(*model.User)
|
||||||
|
store.Must(Srv.Store.User().VerifyEmail(ruser2.Id))
|
||||||
|
|
||||||
Client.LoginByEmail(team.Name, user.Email, user.Password)
|
Client.LoginByEmail(team.Name, user.Email, user.Password)
|
||||||
|
|
||||||
r1, err := Client.GetStatuses()
|
userIds := []string{ruser2.Id}
|
||||||
|
|
||||||
|
r1, err := Client.GetStatuses(userIds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -801,8 +801,8 @@ func (c *Client) ResetPassword(data map[string]string) (*Result, *AppError) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetStatuses() (*Result, *AppError) {
|
func (c *Client) GetStatuses(data []string) (*Result, *AppError) {
|
||||||
if r, err := c.DoApiGet("/users/status", "", ""); err != nil {
|
if r, err := c.DoApiPost("/users/status", ArrayToJson(data)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||||
|
|||||||
@@ -30,19 +30,14 @@ export default class ChannelLoader extends React.Component {
|
|||||||
AsyncClient.getChannels(true, true);
|
AsyncClient.getChannels(true, true);
|
||||||
AsyncClient.getChannelExtraInfo(true);
|
AsyncClient.getChannelExtraInfo(true);
|
||||||
AsyncClient.findTeams();
|
AsyncClient.findTeams();
|
||||||
AsyncClient.getStatuses();
|
|
||||||
AsyncClient.getMyTeam();
|
AsyncClient.getMyTeam();
|
||||||
|
setTimeout(() => AsyncClient.getStatuses(), 3000); // temporary until statuses are reworked a bit
|
||||||
|
|
||||||
/* Perform pending post clean-up */
|
/* Perform pending post clean-up */
|
||||||
PostStore.clearPendingPosts();
|
PostStore.clearPendingPosts();
|
||||||
|
|
||||||
/* Set up interval functions */
|
/* Set up interval functions */
|
||||||
this.intervalId = setInterval(
|
this.intervalId = setInterval(() => AsyncClient.getStatuses(), 30000);
|
||||||
function pollStatuses() {
|
|
||||||
AsyncClient.getStatuses();
|
|
||||||
},
|
|
||||||
30000
|
|
||||||
);
|
|
||||||
|
|
||||||
/* Device tracking setup */
|
/* Device tracking setup */
|
||||||
var iOS = (/(iPad|iPhone|iPod)/g).test(navigator.userAgent);
|
var iOS = (/(iPad|iPhone|iPod)/g).test(navigator.userAgent);
|
||||||
|
|||||||
@@ -100,74 +100,56 @@ 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) {
|
||||||
if (teammate.id === currentUserId) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const channelName = Utils.getDirectChannelName(currentUserId, teammate.id);
|
const member = members[dm.id];
|
||||||
|
const msgCount = dm.total_msg_count - member.msg_count;
|
||||||
|
|
||||||
let forceShow = false;
|
// always show a channel if either it is the current one or if it is unread, but it is not currently being left
|
||||||
let channel = ChannelStore.getByName(channelName);
|
const forceShow = (currentChannelId === dm.id || msgCount > 0) && !this.isLeaving.get(dm.id);
|
||||||
|
const preferenceShow = preferences.some((preference) => (preference.name === teammate.id && preference.value !== 'false'));
|
||||||
|
|
||||||
if (channel) {
|
if (preferenceShow || forceShow) {
|
||||||
const member = members[channel.id];
|
dm.display_name = Utils.displayUsername(teammate.id);
|
||||||
const msgCount = channel.total_msg_count - member.msg_count;
|
dm.teammate_id = teammate.id;
|
||||||
|
dm.status = UserStore.getStatus(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
|
visibleDirectChannels.push(dm);
|
||||||
forceShow = (currentId === channel.id || msgCount > 0) && !this.isLeaving.get(channel.id);
|
|
||||||
} else {
|
|
||||||
channel = {};
|
|
||||||
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);
|
if (forceShow && !preferenceShow) {
|
||||||
channel.teammate_id = teammate.id;
|
// make sure that unread direct channels are visible
|
||||||
channel.status = UserStore.getStatus(teammate.id);
|
const preference = PreferenceStore.setPreference(Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, teammate.id, 'true');
|
||||||
|
AsyncClient.savePreferences([preference]);
|
||||||
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 +516,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,23 @@ 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);
|
||||||
|
if (teammate) {
|
||||||
|
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 +616,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,
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user