[MM-18946] [MM-26721] [MM-6842] Cross team search+private channel autocomplete (#18468)
* Show private channels in autocomplete This is supported in all Engines: MySQL, Postgres, Bleve, Elasticsearch. https://mattermost.atlassian.net/browse/MM-18496 ```release-note Private channels will now appear in channel autocomplete. If you are using Bleve or ElasticSearch, you will have to reindex the channels again to populate them with the new attributes. ``` A large chunk of this work has been based on the earlier effort at https://github.com/mattermost/mattermost-server/pull/17804. Full credit goes to https://github.com/arvinDarmawan. * Add comment ```release-note NONE ``` * Adding more tests ```release-note NONE ``` * fix more tests ```release-note NONE ``` * tmp ```release-note NONE ``` * more fixes ```release-note NONE ``` * add tests ```release-note NONE ``` * Add review comments from previous PR ```release-note NONE ``` * Add API to return all channels from all team ```release-note NONE ``` * Added support for bleve and ES ```release-note NONE ``` * Streaming response for GetAllChannels ```release-note NONE ``` * Fix tests ```release-note NONE ``` * Trigger CI ```release-note NONE ``` * fix tests ```release-note NONE ``` * Addressing review comments ```release-note NONE ``` * Fix lint ```release-note NONE ``` * Removing flaky test ```release-note NONE ``` * Address comments ```release-note NONE ``` * Trigger CI ```release-note NONE ``` * Added /users/<userid>/channel_members endpoint ```release-note NONE ``` * Minor edit ```release-note NONE ``` * Improve embedding ```release-note NONE ``` * Fix lint error ```release-note NONE ``` Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
971af6935c
Коммит
e24f22745e
108
api4/channel.go
108
api4/channel.go
@@ -35,6 +35,7 @@ func (api *API) InitChannel() {
|
||||
api.BaseRoutes.ChannelsForTeam.Handle("/autocomplete", api.APISessionRequired(autocompleteChannelsForTeam)).Methods("GET")
|
||||
api.BaseRoutes.ChannelsForTeam.Handle("/search_autocomplete", api.APISessionRequired(autocompleteChannelsForTeamForSearch)).Methods("GET")
|
||||
api.BaseRoutes.User.Handle("/teams/{team_id:[A-Za-z0-9]+}/channels", api.APISessionRequired(getChannelsForTeamForUser)).Methods("GET")
|
||||
api.BaseRoutes.User.Handle("/channels", api.APISessionRequired(getChannelsForUser)).Methods("GET")
|
||||
|
||||
api.BaseRoutes.ChannelCategories.Handle("", api.APISessionRequired(getCategoriesForTeamForUser)).Methods("GET")
|
||||
api.BaseRoutes.ChannelCategories.Handle("", api.APISessionRequired(createCategoryForTeamForUser)).Methods("POST")
|
||||
@@ -66,7 +67,7 @@ func (api *API) InitChannel() {
|
||||
api.BaseRoutes.ChannelMembers.Handle("", api.APISessionRequired(getChannelMembers)).Methods("GET")
|
||||
api.BaseRoutes.ChannelMembers.Handle("/ids", api.APISessionRequired(getChannelMembersByIds)).Methods("POST")
|
||||
api.BaseRoutes.ChannelMembers.Handle("", api.APISessionRequired(addChannelMember)).Methods("POST")
|
||||
api.BaseRoutes.ChannelMembersForUser.Handle("", api.APISessionRequired(getChannelMembersForUser)).Methods("GET")
|
||||
api.BaseRoutes.ChannelMembersForUser.Handle("", api.APISessionRequired(getChannelMembersForTeamForUser)).Methods("GET")
|
||||
api.BaseRoutes.ChannelMember.Handle("", api.APISessionRequired(getChannelMember)).Methods("GET")
|
||||
api.BaseRoutes.ChannelMember.Handle("", api.APISessionRequired(removeChannelMember)).Methods("DELETE")
|
||||
api.BaseRoutes.ChannelMember.Handle("/roles", api.APISessionRequired(updateChannelMemberRoles)).Methods("PUT")
|
||||
@@ -877,7 +878,7 @@ func getChannelsForTeamForUser(c *Context, w http.ResponseWriter, r *http.Reques
|
||||
return
|
||||
}
|
||||
|
||||
channels, err := c.App.GetChannelsForUser(c.Params.TeamId, c.Params.UserId, c.Params.IncludeDeleted, lastDeleteAt)
|
||||
channels, err := c.App.GetChannelsForTeamForUser(c.Params.TeamId, c.Params.UserId, c.Params.IncludeDeleted, lastDeleteAt)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
@@ -899,6 +900,79 @@ func getChannelsForTeamForUser(c *Context, w http.ResponseWriter, r *http.Reques
|
||||
}
|
||||
}
|
||||
|
||||
func getChannelsForUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.RequireUserId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
|
||||
c.SetPermissionError(model.PermissionEditOtherUsers)
|
||||
return
|
||||
}
|
||||
|
||||
query := r.URL.Query()
|
||||
lastDeleteAt, nErr := strconv.Atoi(query.Get("last_delete_at"))
|
||||
if nErr != nil {
|
||||
lastDeleteAt = 0
|
||||
}
|
||||
if lastDeleteAt < 0 {
|
||||
c.SetInvalidURLParam("last_delete_at")
|
||||
return
|
||||
}
|
||||
|
||||
pageSize := 100
|
||||
fromChannelID := ""
|
||||
// We have to write `[` and `]` separately because we want to stream the response.
|
||||
// The internal API is paginated, but the client always needs to get the full data.
|
||||
// Therefore, to avoid forcing the client to go through all the pages,
|
||||
// we stream the full data from server side itself.
|
||||
//
|
||||
// Note that this means if an error occurs in mid-stream, the response won't be
|
||||
// fully JSON.
|
||||
w.Write([]byte(`[`))
|
||||
enc := json.NewEncoder(w)
|
||||
for {
|
||||
channels, err := c.App.GetChannelsForUser(c.Params.UserId, c.Params.IncludeDeleted, lastDeleteAt, pageSize, fromChannelID)
|
||||
if err != nil {
|
||||
// If the page size was a perfect multiple of the total number of results,
|
||||
// then the last query will always return zero results.
|
||||
if fromChannelID != "" && err.Id == "app.channel.get_channels.not_found.app_error" {
|
||||
break
|
||||
}
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
err = c.App.FillInChannelsProps(channels)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
// intermediary comma between sets
|
||||
if fromChannelID != "" {
|
||||
w.Write([]byte(`,`))
|
||||
}
|
||||
|
||||
for i, ch := range channels {
|
||||
if err := enc.Encode(ch); err != nil {
|
||||
mlog.Warn("Error while writing response", mlog.Err(err))
|
||||
}
|
||||
if i < len(channels)-1 {
|
||||
w.Write([]byte(`,`))
|
||||
}
|
||||
}
|
||||
|
||||
if len(channels) < pageSize {
|
||||
break
|
||||
}
|
||||
|
||||
fromChannelID = channels[len(channels)-1].Id
|
||||
}
|
||||
w.Write([]byte(`]`))
|
||||
}
|
||||
|
||||
func autocompleteChannelsForTeam(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.RequireTeamId()
|
||||
if c.Err != nil {
|
||||
@@ -912,7 +986,7 @@ func autocompleteChannelsForTeam(c *Context, w http.ResponseWriter, r *http.Requ
|
||||
|
||||
name := r.URL.Query().Get("name")
|
||||
|
||||
channels, err := c.App.AutocompleteChannels(c.Params.TeamId, name)
|
||||
channels, err := c.App.AutocompleteChannelsForTeam(c.Params.TeamId, c.AppContext.Session().UserId, name)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
@@ -1029,6 +1103,31 @@ func searchAllChannels(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.SetInvalidParam("channel_search")
|
||||
return
|
||||
}
|
||||
|
||||
fromSysConsole := true
|
||||
if val := r.URL.Query().Get("system_console"); val != "" {
|
||||
fromSysConsole, err = strconv.ParseBool(val)
|
||||
if err != nil {
|
||||
c.SetInvalidParam("system_console")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if !fromSysConsole {
|
||||
// If the request is not coming from system_console, only show the user level channels
|
||||
// from all teams.
|
||||
channels, err := c.App.AutocompleteChannels(c.AppContext.Session().UserId, props.Term)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(channels); err != nil {
|
||||
mlog.Warn("Error while writing response", mlog.Err(err))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Only system managers may use the ExcludePolicyConstrained field
|
||||
if props.ExcludePolicyConstrained && !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleReadComplianceDataRetentionPolicy) {
|
||||
c.SetPermissionError(model.PermissionSysconsoleReadComplianceDataRetentionPolicy)
|
||||
@@ -1039,6 +1138,7 @@ func searchAllChannels(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.SetPermissionError(model.PermissionSysconsoleReadUserManagementChannels)
|
||||
return
|
||||
}
|
||||
|
||||
includeDeleted, _ := strconv.ParseBool(r.URL.Query().Get("include_deleted"))
|
||||
includeDeleted = includeDeleted || props.IncludeDeleted
|
||||
|
||||
@@ -1297,7 +1397,7 @@ func getChannelMember(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func getChannelMembersForUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
func getChannelMembersForTeamForUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.RequireUserId().RequireTeamId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
|
||||
Ссылка в новой задаче
Block a user