From 3ea75332e7502b0d13db5c8c66ebf542607c4e8d Mon Sep 17 00:00:00 2001 From: Max Erenberg Date: Fri, 16 Apr 2021 11:32:09 -0400 Subject: [PATCH] Mm 30807 granular data retention scaffold (#16891) create the necessary tables, models and APIs for the granular data retention policy feature --- api4/channel.go | 46 +- api4/channel_test.go | 96 +++ api4/data_retention.go | 422 ++++++++++- api4/team.go | 60 +- api4/team_test.go | 114 ++- app/analytics.go | 2 +- app/app_iface.go | 28 +- app/channel.go | 33 +- app/data_retention.go | 110 ++- app/helper_test.go | 2 +- app/import_functions_test.go | 2 +- app/layer_generators/main.go | 2 +- app/opentracing/opentracing_layer.go | 456 ++++++++---- app/security_update_check.go | 2 +- app/server.go | 2 +- app/team.go | 64 +- einterfaces/data_retention.go | 16 +- einterfaces/mocks/DataRetentionInterface.go | 313 ++++++++- i18n/en.json | 20 +- model/channel.go | 28 +- model/channel_search.go | 25 +- model/client4.go | 267 ++++++- model/data_retention_policy.go | 115 ++- model/team.go | 1 + model/team_search.go | 17 +- model/utils.go | 6 + services/telemetry/telemetry.go | 2 +- services/telemetry/telemetry_test.go | 2 +- store/opentracinglayer/opentracinglayer.go | 449 +++++++++--- store/retrylayer/retrylayer.go | 495 ++++++++++--- store/retrylayer/retrylayer_test.go | 1 + store/sqlstore/channel_store.go | 52 +- store/sqlstore/channel_store_test.go | 4 +- store/sqlstore/retention_policy_store.go | 609 ++++++++++++++++ store/sqlstore/retention_policy_store_test.go | 14 + store/sqlstore/store.go | 63 +- store/sqlstore/team_store.go | 223 ++---- store/store.go | 67 +- store/storetest/channel_store.go | 38 + store/storetest/mocks/RetentionPolicyStore.go | 374 ++++++++++ store/storetest/mocks/Store.go | 16 + store/storetest/mocks/TeamStore.go | 199 ++---- store/storetest/retention_policy_store.go | 658 ++++++++++++++++++ store/storetest/shared_channel_store.go | 2 +- store/storetest/store.go | 2 + store/storetest/team_store.go | 120 +++- store/timerlayer/timerlayer.go | 403 ++++++++--- web/context.go | 11 + web/params.go | 10 + 49 files changed, 5079 insertions(+), 984 deletions(-) create mode 100644 store/sqlstore/retention_policy_store.go create mode 100644 store/sqlstore/retention_policy_store_test.go create mode 100644 store/storetest/mocks/RetentionPolicyStore.go create mode 100644 store/storetest/retention_policy_store.go diff --git a/api4/channel.go b/api4/channel.go index aadc01c216..c02ff9368d 100644 --- a/api4/channel.go +++ b/api4/channel.go @@ -704,11 +704,20 @@ func getAllChannels(c *Context, w http.ResponseWriter, r *http.Request) { c.SetPermissionError(permissions...) return } + // Only system managers may use the ExcludePolicyConstrained parameter + if c.Params.ExcludePolicyConstrained && !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_SYSCONSOLE_READ_COMPLIANCE_DATA_RETENTION_POLICY) { + c.SetPermissionError(model.PERMISSION_SYSCONSOLE_READ_COMPLIANCE_DATA_RETENTION_POLICY) + return + } opts := model.ChannelSearchOpts{ - NotAssociatedToGroup: c.Params.NotAssociatedToGroup, - ExcludeDefaultChannels: c.Params.ExcludeDefaultChannels, - IncludeDeleted: c.Params.IncludeDeleted, + NotAssociatedToGroup: c.Params.NotAssociatedToGroup, + ExcludeDefaultChannels: c.Params.ExcludeDefaultChannels, + IncludeDeleted: c.Params.IncludeDeleted, + ExcludePolicyConstrained: c.Params.ExcludePolicyConstrained, + } + if c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_SYSCONSOLE_READ_COMPLIANCE_DATA_RETENTION_POLICY) { + opts.IncludePolicyID = true } channels, err := c.App.GetAllChannels(c.Params.Page, c.Params.PerPage, opts) @@ -1013,6 +1022,11 @@ func searchAllChannels(c *Context, w http.ResponseWriter, r *http.Request) { c.SetInvalidParam("channel_search") return } + // Only system managers may use the ExcludePolicyConstrained field + if props.ExcludePolicyConstrained && !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_SYSCONSOLE_READ_COMPLIANCE_DATA_RETENTION_POLICY) { + c.SetPermissionError(model.PERMISSION_SYSCONSOLE_READ_COMPLIANCE_DATA_RETENTION_POLICY) + return + } if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_SYSCONSOLE_READ_USERMANAGEMENT_CHANNELS) { c.SetPermissionError(model.PERMISSION_SYSCONSOLE_READ_USERMANAGEMENT_CHANNELS) @@ -1022,17 +1036,21 @@ func searchAllChannels(c *Context, w http.ResponseWriter, r *http.Request) { includeDeleted = includeDeleted || props.IncludeDeleted opts := model.ChannelSearchOpts{ - NotAssociatedToGroup: props.NotAssociatedToGroup, - ExcludeDefaultChannels: props.ExcludeDefaultChannels, - TeamIds: props.TeamIds, - GroupConstrained: props.GroupConstrained, - ExcludeGroupConstrained: props.ExcludeGroupConstrained, - Public: props.Public, - Private: props.Private, - IncludeDeleted: includeDeleted, - Deleted: props.Deleted, - Page: props.Page, - PerPage: props.PerPage, + NotAssociatedToGroup: props.NotAssociatedToGroup, + ExcludeDefaultChannels: props.ExcludeDefaultChannels, + TeamIds: props.TeamIds, + GroupConstrained: props.GroupConstrained, + ExcludeGroupConstrained: props.ExcludeGroupConstrained, + ExcludePolicyConstrained: props.ExcludePolicyConstrained, + Public: props.Public, + Private: props.Private, + IncludeDeleted: includeDeleted, + Deleted: props.Deleted, + Page: props.Page, + PerPage: props.PerPage, + } + if c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_SYSCONSOLE_READ_COMPLIANCE_DATA_RETENTION_POLICY) { + opts.IncludePolicyID = true } channels, totalCount, appErr := c.App.SearchAllChannels(props.Term, opts) diff --git a/api4/channel_test.go b/api4/channel_test.go index 2eea5419df..03e2f55ad8 100644 --- a/api4/channel_test.go +++ b/api4/channel_test.go @@ -1025,6 +1025,62 @@ func TestGetAllChannels(t *testing.T) { _, resp := Client.GetAllChannels(0, 20, "") CheckForbiddenStatus(t, resp) + + sysManagerChannels, resp := th.SystemManagerClient.GetAllChannels(0, 10000, "") + CheckOKStatus(t, resp) + policyChannel := (*sysManagerChannels)[0] + policy, savePolicyErr := th.App.Srv().Store.RetentionPolicy().Save(&model.RetentionPolicyWithTeamAndChannelIDs{ + RetentionPolicy: model.RetentionPolicy{ + DisplayName: "Policy 1", + PostDuration: model.NewInt64(30), + }, + ChannelIDs: []string{policyChannel.Id}, + }) + require.NoError(t, savePolicyErr) + + t.Run("exclude policy constrained", func(t *testing.T) { + _, resp := th.SystemManagerClient.GetAllChannelsExcludePolicyConstrained(0, 10000, "") + CheckForbiddenStatus(t, resp) + + channels, resp := th.SystemAdminClient.GetAllChannelsExcludePolicyConstrained(0, 10000, "") + CheckOKStatus(t, resp) + found := false + for _, channel := range *channels { + if channel.Id == policyChannel.Id { + found = true + break + } + } + require.False(t, found) + }) + + t.Run("does not return policy ID", func(t *testing.T) { + channels, resp := th.SystemManagerClient.GetAllChannels(0, 10000, "") + CheckOKStatus(t, resp) + found := false + for _, channel := range *channels { + if channel.Id == policyChannel.Id { + found = true + require.Nil(t, channel.PolicyID) + break + } + } + require.True(t, found) + }) + + t.Run("returns policy ID", func(t *testing.T) { + channels, resp := th.SystemAdminClient.GetAllChannels(0, 10000, "") + CheckOKStatus(t, resp) + found := false + for _, channel := range *channels { + if channel.Id == policyChannel.Id { + found = true + require.Equal(t, *channel.PolicyID, policy.ID) + break + } + } + require.True(t, found) + }) } func TestGetAllChannelsWithCount(t *testing.T) { @@ -1390,6 +1446,46 @@ func TestSearchAllChannels(t *testing.T) { _, resp = Client.SearchAllChannels(&model.ChannelSearch{Term: ""}) CheckForbiddenStatus(t, resp) + + // Choose a policy which the system manager can read + sysManagerChannels, resp := th.SystemManagerClient.GetAllChannels(0, 10000, "") + CheckOKStatus(t, resp) + policyChannel := (*sysManagerChannels)[0] + policy, savePolicyErr := th.App.Srv().Store.RetentionPolicy().Save(&model.RetentionPolicyWithTeamAndChannelIDs{ + RetentionPolicy: model.RetentionPolicy{ + DisplayName: "Policy 1", + PostDuration: model.NewInt64(30), + }, + ChannelIDs: []string{policyChannel.Id}, + }) + require.NoError(t, savePolicyErr) + + t.Run("does not return policy ID", func(t *testing.T) { + channels, resp := th.SystemManagerClient.SearchAllChannels(&model.ChannelSearch{Term: policyChannel.Name}) + CheckOKStatus(t, resp) + found := false + for _, channel := range *channels { + if channel.Id == policyChannel.Id { + found = true + require.Nil(t, channel.PolicyID) + break + } + } + require.True(t, found) + }) + t.Run("returns policy ID", func(t *testing.T) { + channels, resp := th.SystemAdminClient.SearchAllChannels(&model.ChannelSearch{Term: policyChannel.Name}) + CheckOKStatus(t, resp) + found := false + for _, channel := range *channels { + if channel.Id == policyChannel.Id { + found = true + require.Equal(t, *channel.PolicyID, policy.ID) + break + } + } + require.True(t, found) + }) } func TestSearchAllChannelsPaged(t *testing.T) { diff --git a/api4/data_retention.go b/api4/data_retention.go index a4e2974463..9fc5280680 100644 --- a/api4/data_retention.go +++ b/api4/data_retention.go @@ -4,21 +4,435 @@ package api4 import ( + "encoding/json" "net/http" + + "github.com/mattermost/mattermost-server/v5/audit" + "github.com/mattermost/mattermost-server/v5/model" ) func (api *API) InitDataRetention() { - api.BaseRoutes.DataRetention.Handle("/policy", api.ApiSessionRequired(getPolicy)).Methods("GET") + api.BaseRoutes.DataRetention.Handle("/policy", api.ApiSessionRequired(getGlobalPolicy)).Methods("GET") + api.BaseRoutes.DataRetention.Handle("/policies", api.ApiSessionRequired(getPolicies)).Methods("GET") + api.BaseRoutes.DataRetention.Handle("/policies_count", api.ApiSessionRequired(getPoliciesCount)).Methods("GET") + api.BaseRoutes.DataRetention.Handle("/policies", api.ApiSessionRequired(createPolicy)).Methods("POST") + api.BaseRoutes.DataRetention.Handle("/policies/{policy_id:[A-Za-z0-9]+}", api.ApiSessionRequired(getPolicy)).Methods("GET") + api.BaseRoutes.DataRetention.Handle("/policies/{policy_id:[A-Za-z0-9]+}", api.ApiSessionRequired(patchPolicy)).Methods("PATCH") + api.BaseRoutes.DataRetention.Handle("/policies/{policy_id:[A-Za-z0-9]+}", api.ApiSessionRequired(deletePolicy)).Methods("DELETE") + api.BaseRoutes.DataRetention.Handle("/policies/{policy_id:[A-Za-z0-9]+}/teams", api.ApiSessionRequired(getTeamsForPolicy)).Methods("GET") + api.BaseRoutes.DataRetention.Handle("/policies/{policy_id:[A-Za-z0-9]+}/teams", api.ApiSessionRequired(addTeamsToPolicy)).Methods("POST") + api.BaseRoutes.DataRetention.Handle("/policies/{policy_id:[A-Za-z0-9]+}/teams", api.ApiSessionRequired(removeTeamsFromPolicy)).Methods("DELETE") + api.BaseRoutes.DataRetention.Handle("/policies/{policy_id:[A-Za-z0-9]+}/teams/search", api.ApiSessionRequired(searchTeamsInPolicy)).Methods("POST") + api.BaseRoutes.DataRetention.Handle("/policies/{policy_id:[A-Za-z0-9]+}/channels", api.ApiSessionRequired(getChannelsForPolicy)).Methods("GET") + api.BaseRoutes.DataRetention.Handle("/policies/{policy_id:[A-Za-z0-9]+}/channels", api.ApiSessionRequired(addChannelsToPolicy)).Methods("POST") + api.BaseRoutes.DataRetention.Handle("/policies/{policy_id:[A-Za-z0-9]+}/channels", api.ApiSessionRequired(removeChannelsFromPolicy)).Methods("DELETE") + api.BaseRoutes.DataRetention.Handle("/policies/{policy_id:[A-Za-z0-9]+}/channels/search", api.ApiSessionRequired(searchChannelsInPolicy)).Methods("POST") + api.BaseRoutes.User.Handle("/data_retention/team_policies", api.ApiSessionRequired(getTeamPoliciesForUser)).Methods("GET") + api.BaseRoutes.User.Handle("/data_retention/channel_policies", api.ApiSessionRequired(getChannelPoliciesForUser)).Methods("GET") } -func getPolicy(c *Context, w http.ResponseWriter, r *http.Request) { +func getGlobalPolicy(c *Context, w http.ResponseWriter, r *http.Request) { // No permission check required. - policy, err := c.App.GetDataRetentionPolicy() + policy, err := c.App.GetGlobalRetentionPolicy() if err != nil { c.Err = err return } - w.Write([]byte(policy.ToJson())) + w.Write(policy.ToJson()) +} + +func getPolicies(c *Context, w http.ResponseWriter, r *http.Request) { + if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_SYSCONSOLE_READ_COMPLIANCE_DATA_RETENTION_POLICY) { + c.SetPermissionError(model.PERMISSION_SYSCONSOLE_READ_COMPLIANCE_DATA_RETENTION_POLICY) + return + } + + limit := c.Params.PerPage + offset := c.Params.Page * limit + + policies, err := c.App.GetRetentionPolicies(offset, limit) + if err != nil { + c.Err = err + return + } + + w.Write(policies.ToJson()) +} + +func getPoliciesCount(c *Context, w http.ResponseWriter, r *http.Request) { + if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_SYSCONSOLE_READ_COMPLIANCE_DATA_RETENTION_POLICY) { + c.SetPermissionError(model.PERMISSION_SYSCONSOLE_READ_COMPLIANCE_DATA_RETENTION_POLICY) + return + } + + count, err := c.App.GetRetentionPoliciesCount() + if err != nil { + c.Err = err + return + } + body := map[string]int64{"total_count": count} + b, _ := json.Marshal(body) + w.Write(b) +} + +func getPolicy(c *Context, w http.ResponseWriter, r *http.Request) { + if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_SYSCONSOLE_READ_COMPLIANCE_DATA_RETENTION_POLICY) { + c.SetPermissionError(model.PERMISSION_SYSCONSOLE_READ_COMPLIANCE_DATA_RETENTION_POLICY) + return + } + + c.RequirePolicyId() + policy, err := c.App.GetRetentionPolicy(c.Params.PolicyId) + if err != nil { + c.Err = err + return + } + w.Write(policy.ToJson()) +} + +func createPolicy(c *Context, w http.ResponseWriter, r *http.Request) { + policy, jsonErr := model.RetentionPolicyWithTeamAndChannelIdsFromJson(r.Body) + if jsonErr != nil { + c.SetInvalidParam("policy") + return + } + auditRec := c.MakeAuditRecord("createPolicy", audit.Fail) + defer c.LogAuditRec(auditRec) + auditRec.AddMeta("policy", policy) + + if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_SYSCONSOLE_WRITE_COMPLIANCE_DATA_RETENTION_POLICY) { + c.SetPermissionError(model.PERMISSION_SYSCONSOLE_WRITE_COMPLIANCE_DATA_RETENTION_POLICY) + return + } + + newPolicy, err := c.App.CreateRetentionPolicy(policy) + if err != nil { + c.Err = err + return + } + + auditRec.AddMeta("policy", newPolicy) // overwrite meta + auditRec.Success() + w.WriteHeader(http.StatusCreated) + w.Write(newPolicy.ToJson()) +} + +func patchPolicy(c *Context, w http.ResponseWriter, r *http.Request) { + patch, jsonErr := model.RetentionPolicyWithTeamAndChannelIdsFromJson(r.Body) + if jsonErr != nil { + c.SetInvalidParam("policy") + } + c.RequirePolicyId() + patch.ID = c.Params.PolicyId + + auditRec := c.MakeAuditRecord("patchPolicy", audit.Fail) + defer c.LogAuditRec(auditRec) + auditRec.AddMeta("patch", patch) + + if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_SYSCONSOLE_WRITE_COMPLIANCE_DATA_RETENTION_POLICY) { + c.SetPermissionError(model.PERMISSION_SYSCONSOLE_WRITE_COMPLIANCE_DATA_RETENTION_POLICY) + return + } + + policy, err := c.App.PatchRetentionPolicy(patch) + if err != nil { + c.Err = err + return + } + auditRec.Success() + w.Write(policy.ToJson()) +} + +func deletePolicy(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequirePolicyId() + policyId := c.Params.PolicyId + + auditRec := c.MakeAuditRecord("deletePolicy", audit.Fail) + defer c.LogAuditRec(auditRec) + auditRec.AddMeta("policy_id", policyId) + if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_SYSCONSOLE_WRITE_COMPLIANCE_DATA_RETENTION_POLICY) { + c.SetPermissionError(model.PERMISSION_SYSCONSOLE_WRITE_COMPLIANCE_DATA_RETENTION_POLICY) + return + } + + err := c.App.DeleteRetentionPolicy(policyId) + if err != nil { + c.Err = err + return + } + auditRec.Success() + ReturnStatusOK(w) +} + +func getTeamsForPolicy(c *Context, w http.ResponseWriter, r *http.Request) { + if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_SYSCONSOLE_READ_COMPLIANCE_DATA_RETENTION_POLICY) { + c.SetPermissionError(model.PERMISSION_SYSCONSOLE_READ_COMPLIANCE_DATA_RETENTION_POLICY) + return + } + + c.RequirePolicyId() + policyId := c.Params.PolicyId + limit := c.Params.PerPage + offset := c.Params.Page * limit + + teams, err := c.App.GetTeamsForRetentionPolicy(policyId, offset, limit) + if err != nil { + c.Err = err + return + } + + b, jsonErr := json.Marshal(teams) + if jsonErr != nil { + c.Err = model.NewAppError("Api4.getTeamsForPolicy", "api.marshal_error", nil, jsonErr.Error(), http.StatusInternalServerError) + return + } + w.Write(b) +} + +func searchTeamsInPolicy(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequirePolicyId() + + if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_SYSCONSOLE_READ_COMPLIANCE_DATA_RETENTION_POLICY) { + c.SetPermissionError(model.PERMISSION_SYSCONSOLE_READ_COMPLIANCE_DATA_RETENTION_POLICY) + return + } + + props := model.TeamSearchFromJson(r.Body) + if props == nil { + c.SetInvalidParam("team_search") + return + } + props.PolicyID = model.NewString(c.Params.PolicyId) + props.IncludePolicyID = model.NewBool(true) + + teams, _, err := c.App.SearchAllTeams(props) + if err != nil { + c.Err = err + return + } + c.App.SanitizeTeams(*c.App.Session(), teams) + + payload := []byte(model.TeamListToJson(teams)) + w.Write(payload) +} + +func addTeamsToPolicy(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequirePolicyId() + policyId := c.Params.PolicyId + var teamIDs []string + jsonErr := json.NewDecoder(r.Body).Decode(&teamIDs) + if jsonErr != nil { + c.SetInvalidParam("team_ids") + return + } + auditRec := c.MakeAuditRecord("addTeamsToPolicy", audit.Fail) + defer c.LogAuditRec(auditRec) + auditRec.AddMeta("policy_id", policyId) + auditRec.AddMeta("team_ids", teamIDs) + if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_SYSCONSOLE_WRITE_COMPLIANCE_DATA_RETENTION_POLICY) { + c.SetPermissionError(model.PERMISSION_SYSCONSOLE_WRITE_COMPLIANCE_DATA_RETENTION_POLICY) + return + } + + err := c.App.AddTeamsToRetentionPolicy(policyId, teamIDs) + if err != nil { + c.Err = err + return + } + + auditRec.Success() + ReturnStatusOK(w) +} + +func removeTeamsFromPolicy(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequirePolicyId() + policyId := c.Params.PolicyId + var teamIDs []string + jsonErr := json.NewDecoder(r.Body).Decode(&teamIDs) + if jsonErr != nil { + c.SetInvalidParam("team_ids") + return + } + auditRec := c.MakeAuditRecord("removeTeamsFromPolicy", audit.Fail) + defer c.LogAuditRec(auditRec) + auditRec.AddMeta("policy_id", policyId) + auditRec.AddMeta("team_ids", teamIDs) + + if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_SYSCONSOLE_WRITE_COMPLIANCE_DATA_RETENTION_POLICY) { + c.SetPermissionError(model.PERMISSION_SYSCONSOLE_WRITE_COMPLIANCE_DATA_RETENTION_POLICY) + return + } + + err := c.App.RemoveTeamsFromRetentionPolicy(policyId, teamIDs) + if err != nil { + c.Err = err + return + } + + auditRec.Success() + ReturnStatusOK(w) +} + +func getChannelsForPolicy(c *Context, w http.ResponseWriter, r *http.Request) { + if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_SYSCONSOLE_READ_COMPLIANCE_DATA_RETENTION_POLICY) { + c.SetPermissionError(model.PERMISSION_SYSCONSOLE_READ_COMPLIANCE_DATA_RETENTION_POLICY) + return + } + + c.RequirePolicyId() + policyId := c.Params.PolicyId + limit := c.Params.PerPage + offset := c.Params.Page * limit + + channels, err := c.App.GetChannelsForRetentionPolicy(policyId, offset, limit) + if err != nil { + c.Err = err + return + } + + b, jsonErr := json.Marshal(channels) + if jsonErr != nil { + c.Err = model.NewAppError("Api4.getChannelsForPolicy", "api.marshal_error", nil, jsonErr.Error(), http.StatusInternalServerError) + return + } + w.Write(b) +} + +func searchChannelsInPolicy(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequirePolicyId() + props := model.ChannelSearchFromJson(r.Body) + if props == nil { + c.SetInvalidParam("channel_search") + return + } + + if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_SYSCONSOLE_READ_COMPLIANCE_DATA_RETENTION_POLICY) { + c.SetPermissionError(model.PERMISSION_SYSCONSOLE_READ_COMPLIANCE_DATA_RETENTION_POLICY) + return + } + + opts := model.ChannelSearchOpts{ + PolicyID: c.Params.PolicyId, + IncludePolicyID: true, + Deleted: props.Deleted, + IncludeDeleted: props.IncludeDeleted, + Public: props.Public, + Private: props.Private, + TeamIds: props.TeamIds, + } + + channels, _, appErr := c.App.SearchAllChannels(props.Term, opts) + if appErr != nil { + c.Err = appErr + return + } + + payload := []byte(channels.ToJson()) + w.Write(payload) +} + +func addChannelsToPolicy(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequirePolicyId() + policyId := c.Params.PolicyId + var channelIDs []string + jsonErr := json.NewDecoder(r.Body).Decode(&channelIDs) + if jsonErr != nil { + c.SetInvalidParam("channel_ids") + return + } + auditRec := c.MakeAuditRecord("addChannelsToPolicy", audit.Fail) + defer c.LogAuditRec(auditRec) + auditRec.AddMeta("policy_id", policyId) + auditRec.AddMeta("channel_ids", channelIDs) + + if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_SYSCONSOLE_WRITE_COMPLIANCE_DATA_RETENTION_POLICY) { + c.SetPermissionError(model.PERMISSION_SYSCONSOLE_WRITE_COMPLIANCE_DATA_RETENTION_POLICY) + return + } + + err := c.App.AddChannelsToRetentionPolicy(policyId, channelIDs) + if err != nil { + c.Err = err + return + } + + auditRec.Success() + ReturnStatusOK(w) +} + +func removeChannelsFromPolicy(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequirePolicyId() + policyId := c.Params.PolicyId + var channelIDs []string + jsonErr := json.NewDecoder(r.Body).Decode(&channelIDs) + if jsonErr != nil { + c.SetInvalidParam("channel_ids") + return + } + auditRec := c.MakeAuditRecord("removeChannelsFromPolicy", audit.Fail) + defer c.LogAuditRec(auditRec) + auditRec.AddMeta("policy_id", policyId) + auditRec.AddMeta("channel_ids", channelIDs) + + if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_SYSCONSOLE_WRITE_COMPLIANCE_DATA_RETENTION_POLICY) { + c.SetPermissionError(model.PERMISSION_SYSCONSOLE_WRITE_COMPLIANCE_DATA_RETENTION_POLICY) + return + } + + err := c.App.RemoveChannelsFromRetentionPolicy(policyId, channelIDs) + if err != nil { + c.Err = err + return + } + + auditRec.Success() + ReturnStatusOK(w) +} + +func getTeamPoliciesForUser(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequireUserId() + if c.Err != nil { + return + } + userID := c.Params.UserId + limit := c.Params.PerPage + offset := c.Params.Page * limit + + if userID != c.App.Session().UserId && !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_MANAGE_SYSTEM) { + c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) + return + } + + policies, err := c.App.GetTeamPoliciesForUser(userID, offset, limit) + if err != nil { + c.Err = err + return + } + + w.Write(policies.ToJson()) +} + +func getChannelPoliciesForUser(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequireUserId() + if c.Err != nil { + return + } + userID := c.Params.UserId + limit := c.Params.PerPage + offset := c.Params.Page * limit + + if userID != c.App.Session().UserId && !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_MANAGE_SYSTEM) { + c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) + return + } + + policies, err := c.App.GetChannelPoliciesForUser(userID, offset, limit) + if err != nil { + c.Err = err + return + } + + w.Write(policies.ToJson()) } diff --git a/api4/team.go b/api4/team.go index 258d93a73b..64aa726e41 100644 --- a/api4/team.go +++ b/api4/team.go @@ -955,29 +955,37 @@ func getAllTeams(c *Context, w http.ResponseWriter, r *http.Request) { var err *model.AppError var teamsWithCount *model.TeamsWithCount + opts := &model.TeamSearch{} + if c.Params.ExcludePolicyConstrained { + if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_SYSCONSOLE_READ_COMPLIANCE_DATA_RETENTION_POLICY) { + c.SetPermissionError(model.PERMISSION_SYSCONSOLE_READ_COMPLIANCE_DATA_RETENTION_POLICY) + return + } + opts.ExcludePolicyConstrained = model.NewBool(true) + } + if c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_SYSCONSOLE_READ_COMPLIANCE_DATA_RETENTION_POLICY) { + opts.IncludePolicyID = model.NewBool(true) + } + listPrivate := c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_LIST_PRIVATE_TEAMS) listPublic := c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_LIST_PUBLIC_TEAMS) + limit := c.Params.PerPage + offset := limit * c.Params.Page if listPrivate && listPublic { - if c.Params.IncludeTotalCount { - teamsWithCount, err = c.App.GetAllTeamsPageWithCount(c.Params.Page*c.Params.PerPage, c.Params.PerPage) - } else { - teams, err = c.App.GetAllTeamsPage(c.Params.Page*c.Params.PerPage, c.Params.PerPage) - } } else if listPrivate { - if c.Params.IncludeTotalCount { - teamsWithCount, err = c.App.GetAllPrivateTeamsPageWithCount(c.Params.Page*c.Params.PerPage, c.Params.PerPage) - } else { - teams, err = c.App.GetAllPrivateTeamsPage(c.Params.Page*c.Params.PerPage, c.Params.PerPage) - } + opts.AllowOpenInvite = model.NewBool(false) } else if listPublic { - if c.Params.IncludeTotalCount { - teamsWithCount, err = c.App.GetAllPublicTeamsPageWithCount(c.Params.Page*c.Params.PerPage, c.Params.PerPage) - } else { - teams, err = c.App.GetAllPublicTeamsPage(c.Params.Page*c.Params.PerPage, c.Params.PerPage) - } + opts.AllowOpenInvite = model.NewBool(true) } else { // The user doesn't have permissions to list private as well as public teams. - err = model.NewAppError("getAllTeams", "api.team.get_all_teams.insufficient_permissions", nil, "", http.StatusForbidden) + c.Err = model.NewAppError("getAllTeams", "api.team.get_all_teams.insufficient_permissions", nil, "", http.StatusForbidden) + return + } + + if c.Params.IncludeTotalCount { + teamsWithCount, err = c.App.GetAllTeamsPageWithCount(offset, limit, opts) + } else { + teams, err = c.App.GetAllTeamsPage(offset, limit, opts) } if err != nil { c.Err = err @@ -991,7 +999,7 @@ func getAllTeams(c *Context, w http.ResponseWriter, r *http.Request) { if c.Params.IncludeTotalCount { resBody = model.TeamsWithCountToJson(teamsWithCount) } else { - resBody = []byte(model.TeamListToJson(teams)) + resBody = model.ToJson(teams) } w.Write(resBody) @@ -1003,6 +1011,16 @@ func searchTeams(c *Context, w http.ResponseWriter, r *http.Request) { c.SetInvalidParam("team_search") return } + // Only system managers may use the ExcludePolicyConstrained field + if props.ExcludePolicyConstrained != nil && !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_SYSCONSOLE_READ_COMPLIANCE_DATA_RETENTION_POLICY) { + c.SetPermissionError(model.PERMISSION_SYSCONSOLE_READ_COMPLIANCE_DATA_RETENTION_POLICY) + return + } + // policy ID may only be used through the /data_retention/policies endpoint + props.PolicyID = nil + if c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_SYSCONSOLE_READ_COMPLIANCE_DATA_RETENTION_POLICY) { + props.IncludePolicyID = model.NewBool(true) + } var teams []*model.Team var totalCount int64 @@ -1015,13 +1033,13 @@ func searchTeams(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = model.NewAppError("searchTeams", "api.team.search_teams.pagination_not_implemented.private_team_search", nil, "", http.StatusNotImplemented) return } - teams, err = c.App.SearchPrivateTeams(props.Term) + teams, err = c.App.SearchPrivateTeams(props) } else if c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_LIST_PUBLIC_TEAMS) { if props.Page != nil || props.PerPage != nil { c.Err = model.NewAppError("searchTeams", "api.team.search_teams.pagination_not_implemented.public_team_search", nil, "", http.StatusNotImplemented) return } - teams, err = c.App.SearchPublicTeams(props.Term) + teams, err = c.App.SearchPublicTeams(props) } else { teams = []*model.Team{} } @@ -1035,8 +1053,8 @@ func searchTeams(c *Context, w http.ResponseWriter, r *http.Request) { var payload []byte if props.Page != nil && props.PerPage != nil { - twc := &model.TeamsWithCount{Teams: teams, TotalCount: totalCount} - payload = model.TeamsWithCountToJson(twc) + twc := map[string]interface{}{"teams": teams, "total_count": totalCount} + payload = model.ToJson(twc) } else { payload = []byte(model.TeamListToJson(teams)) } diff --git a/api4/team_test.go b/api4/team_test.go index 5766021616..5a637f5945 100644 --- a/api4/team_test.go +++ b/api4/team_test.go @@ -1007,6 +1007,76 @@ func TestGetAllTeams(t *testing.T) { require.Len(t, teams, 5) }) + // Choose a team which the system manager can access + sysManagerTeams, resp := th.SystemManagerClient.GetAllTeams("", 0, 10000) + CheckOKStatus(t, resp) + policyTeam := sysManagerTeams[0] + // If no policies exist, GetAllTeamsExcludePolicyConstrained should return everything + t.Run("exclude policy constrained, without policy", func(t *testing.T) { + _, excludeConstrainedResp := Client.GetAllTeamsExcludePolicyConstrained("", 0, 100) + CheckForbiddenStatus(t, excludeConstrainedResp) + teams, excludeConstrainedResp := th.SystemAdminClient.GetAllTeamsExcludePolicyConstrained("", 0, 100) + CheckOKStatus(t, excludeConstrainedResp) + found := false + for _, team := range teams { + if team.Id == policyTeam.Id { + found = true + break + } + } + require.True(t, found) + }) + // Now actually create the policy and assign the team to it + policy, savePolicyErr := th.App.Srv().Store.RetentionPolicy().Save(&model.RetentionPolicyWithTeamAndChannelIDs{ + RetentionPolicy: model.RetentionPolicy{ + DisplayName: "Policy 1", + PostDuration: model.NewInt64(30), + }, + TeamIDs: []string{policyTeam.Id}, + }) + require.NoError(t, savePolicyErr) + // This time, the team shouldn't be returned + t.Run("exclude policy constrained, with policy", func(t *testing.T) { + teams, excludeConstrainedResp := th.SystemAdminClient.GetAllTeamsExcludePolicyConstrained("", 0, 100) + CheckOKStatus(t, excludeConstrainedResp) + found := false + for _, team := range teams { + if team.Id == policyTeam.Id { + found = true + break + } + } + require.False(t, found) + }) + + t.Run("does not return policy ID", func(t *testing.T) { + teams, sysManagerResp := th.SystemManagerClient.GetAllTeams("", 0, 100) + CheckOKStatus(t, sysManagerResp) + found := false + for _, team := range teams { + if team.Id == policyTeam.Id { + found = true + require.Nil(t, team.PolicyID) + break + } + } + require.True(t, found) + }) + + t.Run("returns policy ID", func(t *testing.T) { + teams, sysAdminResp := th.SystemAdminClient.GetAllTeams("", 0, 100) + CheckOKStatus(t, sysAdminResp) + found := false + for _, team := range teams { + if team.Id == policyTeam.Id { + found = true + require.Equal(t, *team.PolicyID, policy.ID) + break + } + } + require.True(t, found) + }) + t.Run("Unauthorized", func(t *testing.T) { Client.Logout() _, resp = Client.GetAllTeams("", 1, 10) @@ -1215,7 +1285,7 @@ func TestSearchAllTeams(t *testing.T) { th.LoginBasic() th.TestForAllClients(t, func(t *testing.T, client *model.Client4) { - rteams, resp := client.SearchTeams(&model.TeamSearch{Term: oTeam.Name}) + rteams, resp = client.SearchTeams(&model.TeamSearch{Term: oTeam.Name}) CheckNoError(t, resp) require.Len(t, rteams, 1, "should have returned 1 team") require.Equal(t, oTeam.Id, rteams[0].Id, "invalid team") @@ -1231,7 +1301,7 @@ func TestSearchAllTeams(t *testing.T) { }) th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) { - rteams, resp := client.SearchTeams(&model.TeamSearch{Term: oTeam.Name}) + rteams, resp = client.SearchTeams(&model.TeamSearch{Term: oTeam.Name}) CheckNoError(t, resp) require.Len(t, rteams, 1, "should have returned 1 team") @@ -1239,6 +1309,46 @@ func TestSearchAllTeams(t *testing.T) { CheckNoError(t, resp) require.Len(t, rteams, 1, "should have returned 1 team") }) + + // Choose a team which the system manager can access + sysManagerTeams, resp := th.SystemManagerClient.GetAllTeams("", 0, 10000) + CheckOKStatus(t, resp) + policyTeam := sysManagerTeams[0] + // Now actually create the policy and assign the team to it + policy, savePolicyErr := th.App.Srv().Store.RetentionPolicy().Save(&model.RetentionPolicyWithTeamAndChannelIDs{ + RetentionPolicy: model.RetentionPolicy{ + DisplayName: "Policy 1", + PostDuration: model.NewInt64(30), + }, + TeamIDs: []string{policyTeam.Id}, + }) + require.NoError(t, savePolicyErr) + t.Run("does not return policy ID", func(t *testing.T) { + teams, sysManagerResp := th.SystemManagerClient.SearchTeams(&model.TeamSearch{Term: policyTeam.Name}) + CheckOKStatus(t, sysManagerResp) + found := false + for _, team := range teams { + if team.Id == policyTeam.Id { + found = true + require.Nil(t, team.PolicyID) + break + } + } + require.True(t, found) + }) + t.Run("returns policy ID", func(t *testing.T) { + teams, sysAdminResp := th.SystemAdminClient.SearchTeams(&model.TeamSearch{Term: policyTeam.Name}) + CheckOKStatus(t, sysAdminResp) + found := false + for _, team := range teams { + if team.Id == policyTeam.Id { + found = true + require.Equal(t, *team.PolicyID, policy.ID) + break + } + } + require.True(t, found) + }) } func TestSearchAllTeamsPaged(t *testing.T) { diff --git a/app/analytics.go b/app/analytics.go index 96e33785b7..572af594a3 100644 --- a/app/analytics.go +++ b/app/analytics.go @@ -97,7 +97,7 @@ func (a *App) GetAnalytics(name string, teamID string) (model.AnalyticsRows, *mo var teamsCount int64 g.Go(func() error { var err error - if teamsCount, err = a.Srv().Store.Team().AnalyticsTeamCount(false); err != nil { + if teamsCount, err = a.Srv().Store.Team().AnalyticsTeamCount(nil); err != nil { return model.NewAppError("GetAnalytics", "app.team.analytics_team_count.app_error", nil, err.Error(), http.StatusInternalServerError) } return nil diff --git a/app/app_iface.go b/app/app_iface.go index 44a4e5643e..559402555b 100644 --- a/app/app_iface.go +++ b/app/app_iface.go @@ -365,6 +365,7 @@ type AppIface interface { AcceptLanguage() string AccountMigration() einterfaces.AccountMigrationInterface ActivateMfa(userID, token string) *model.AppError + AddChannelsToRetentionPolicy(policyID string, channelIDs []string) *model.AppError AddConfigListener(listener func(*model.Config, *model.Config)) string AddDirectChannels(teamID string, user *model.User) *model.AppError AddLdapPrivateCertificate(fileData *multipart.FileHeader) *model.AppError @@ -380,6 +381,7 @@ type AppIface interface { AddTeamMemberByInviteId(inviteId, userID string) (*model.TeamMember, *model.AppError) AddTeamMemberByToken(userID, tokenID string) (*model.TeamMember, *model.AppError) AddTeamMembers(teamID string, userIDs []string, userRequestorId string, graceful bool) ([]*model.TeamMemberWithError, *model.AppError) + AddTeamsToRetentionPolicy(policyID string, teamIDs []string) *model.AppError AddUserToTeam(teamID string, userID string, userRequestorId string) (*model.Team, *model.TeamMember, *model.AppError) AddUserToTeamByInviteId(inviteId string, userID string) (*model.Team, *model.TeamMember, *model.AppError) AddUserToTeamByTeamId(teamID string, user *model.User) *model.AppError @@ -452,6 +454,7 @@ type AppIface interface { CreatePost(post *model.Post, channel *model.Channel, triggerWebhooks, setOnline bool) (savedPost *model.Post, err *model.AppError) CreatePostAsUser(post *model.Post, currentSessionId string, setOnline bool) (*model.Post, *model.AppError) CreatePostMissingChannel(post *model.Post, triggerWebhooks bool) (*model.Post, *model.AppError) + CreateRetentionPolicy(policy *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, *model.AppError) CreateRole(role *model.Role) (*model.Role, *model.AppError) CreateScheme(scheme *model.Scheme) (*model.Scheme, *model.AppError) CreateSession(session *model.Session) (*model.Session, *model.AppError) @@ -493,6 +496,7 @@ type AppIface interface { DeletePreferences(userID string, preferences model.Preferences) *model.AppError DeleteReactionForPost(reaction *model.Reaction) *model.AppError DeleteRemoteCluster(remoteClusterId string) (bool, *model.AppError) + DeleteRetentionPolicy(policyID string) *model.AppError DeleteScheme(schemeId string) (*model.Scheme, *model.AppError) DeleteSharedChannel(channelID string) (bool, error) DeleteSharedChannelRemote(id string) (bool, error) @@ -532,17 +536,13 @@ type AppIface interface { GetAllChannels(page, perPage int, opts model.ChannelSearchOpts) (*model.ChannelListWithTeamData, *model.AppError) GetAllChannelsCount(opts model.ChannelSearchOpts) (int64, *model.AppError) GetAllPrivateTeams() ([]*model.Team, *model.AppError) - GetAllPrivateTeamsPage(offset int, limit int) ([]*model.Team, *model.AppError) - GetAllPrivateTeamsPageWithCount(offset int, limit int) (*model.TeamsWithCount, *model.AppError) GetAllPublicTeams() ([]*model.Team, *model.AppError) - GetAllPublicTeamsPage(offset int, limit int) ([]*model.Team, *model.AppError) - GetAllPublicTeamsPageWithCount(offset int, limit int) (*model.TeamsWithCount, *model.AppError) GetAllRemoteClusters(filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, *model.AppError) GetAllRoles() ([]*model.Role, *model.AppError) GetAllStatuses() map[string]*model.Status GetAllTeams() ([]*model.Team, *model.AppError) - GetAllTeamsPage(offset int, limit int) ([]*model.Team, *model.AppError) - GetAllTeamsPageWithCount(offset int, limit int) (*model.TeamsWithCount, *model.AppError) + GetAllTeamsPage(offset int, limit int, opts *model.TeamSearch) ([]*model.Team, *model.AppError) + GetAllTeamsPageWithCount(offset int, limit int, opts *model.TeamSearch) (*model.TeamsWithCount, *model.AppError) GetAnalytics(name string, teamID string) (model.AnalyticsRows, *model.AppError) GetAudits(userID string, limit int) (model.Audits, *model.AppError) GetAuditsPage(userID string, page int, perPage int) (model.Audits, *model.AppError) @@ -563,8 +563,10 @@ type AppIface interface { GetChannelMembersPage(channelID string, page, perPage int) (*model.ChannelMembers, *model.AppError) GetChannelMembersTimezones(channelID string) ([]string, *model.AppError) GetChannelPinnedPostCount(channelID string) (int64, *model.AppError) + GetChannelPoliciesForUser(userID string, offset, limit int) (*model.RetentionPolicyForChannelList, *model.AppError) GetChannelUnread(channelID, userID string) (*model.ChannelUnread, *model.AppError) GetChannelsByNames(channelNames []string, teamID string) ([]*model.Channel, *model.AppError) + GetChannelsForRetentionPolicy(policyID string, offset, limit int) (*model.ChannelsWithCount, *model.AppError) GetChannelsForScheme(scheme *model.Scheme, offset int, limit int) (model.ChannelList, *model.AppError) GetChannelsForSchemePage(scheme *model.Scheme, page int, perPage int) (model.ChannelList, *model.AppError) GetChannelsForUser(teamID string, userID string, includeDeleted bool, lastDeleteAt int) (*model.ChannelList, *model.AppError) @@ -577,7 +579,6 @@ type AppIface interface { GetComplianceReport(reportId string) (*model.Compliance, *model.AppError) GetComplianceReports(page, perPage int) (model.Compliances, *model.AppError) GetCookieDomain() string - GetDataRetentionPolicy() (*model.DataRetentionPolicy, *model.AppError) GetDefaultProfileImage(user *model.User) ([]byte, *model.AppError) GetDeletedChannels(teamID string, offset int, limit int, userID string) (*model.ChannelList, *model.AppError) GetEmoji(emojiId string) (*model.Emoji, *model.AppError) @@ -593,6 +594,7 @@ type AppIface interface { GetFlaggedPosts(userID string, offset int, limit int) (*model.PostList, *model.AppError) GetFlaggedPostsForChannel(userID, channelID string, offset int, limit int) (*model.PostList, *model.AppError) GetFlaggedPostsForTeam(userID, teamID string, offset int, limit int) (*model.PostList, *model.AppError) + GetGlobalRetentionPolicy() (*model.GlobalRetentionPolicy, *model.AppError) GetGroup(id string) (*model.Group, *model.AppError) GetGroupByName(name string, opts model.GroupSearchOpts) (*model.Group, *model.AppError) GetGroupByRemoteID(remoteID string, groupSource model.GroupSource) (*model.Group, *model.AppError) @@ -681,6 +683,9 @@ type AppIface interface { GetRemoteClusterForUser(remoteID string, userID string) (*model.RemoteCluster, *model.AppError) GetRemoteClusterService() (remotecluster.RemoteClusterServiceIFace, *model.AppError) GetRemoteClusterSession(token string, remoteId string) (*model.Session, *model.AppError) + GetRetentionPolicies(offset, limit int) (*model.RetentionPolicyWithTeamAndChannelCountsList, *model.AppError) + GetRetentionPoliciesCount() (int64, *model.AppError) + GetRetentionPolicy(policyID string) (*model.RetentionPolicyWithTeamAndChannelCounts, *model.AppError) GetRole(id string) (*model.Role, *model.AppError) GetRoleByName(name string) (*model.Role, *model.AppError) GetRolesByNames(names []string) ([]*model.Role, *model.AppError) @@ -722,8 +727,10 @@ type AppIface interface { GetTeamMembersByIds(teamID string, userIDs []string, restrictions *model.ViewUsersRestrictions) ([]*model.TeamMember, *model.AppError) GetTeamMembersForUser(userID string) ([]*model.TeamMember, *model.AppError) GetTeamMembersForUserWithPagination(userID string, page, perPage int) ([]*model.TeamMember, *model.AppError) + GetTeamPoliciesForUser(userID string, offset, limit int) (*model.RetentionPolicyForTeamList, *model.AppError) GetTeamStats(teamID string, restrictions *model.ViewUsersRestrictions) (*model.TeamStats, *model.AppError) GetTeamUnread(teamID, userID string) (*model.TeamUnread, *model.AppError) + GetTeamsForRetentionPolicy(policyID string, offset, limit int) (*model.TeamsWithCount, *model.AppError) GetTeamsForScheme(scheme *model.Scheme, offset int, limit int) ([]*model.Team, *model.AppError) GetTeamsForSchemePage(scheme *model.Scheme, page int, perPage int) ([]*model.Team, *model.AppError) GetTeamsForUser(userID string) ([]*model.Team, *model.AppError) @@ -837,6 +844,7 @@ type AppIface interface { OriginChecker() func(*http.Request) bool PatchChannel(channel *model.Channel, patch *model.ChannelPatch, userID string) (*model.Channel, *model.AppError) PatchPost(postID string, patch *model.PostPatch) (*model.Post, *model.AppError) + PatchRetentionPolicy(patch *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, *model.AppError) PatchRole(role *model.Role, patch *model.RolePatch) (*model.Role, *model.AppError) PatchScheme(scheme *model.Scheme, patch *model.SchemePatch) (*model.Scheme, *model.AppError) PatchTeam(teamID string, patch *model.TeamPatch) (*model.Team, *model.AppError) @@ -873,6 +881,7 @@ type AppIface interface { RegisterPluginCommand(pluginID string, command *model.Command) error ReloadConfig() error RemoveAllDeactivatedMembersFromChannel(channel *model.Channel) *model.AppError + RemoveChannelsFromRetentionPolicy(policyID string, channelIDs []string) *model.AppError RemoveConfigListener(id string) RemoveCustomStatus(userID string) *model.AppError RemoveDirectory(path string) *model.AppError @@ -887,6 +896,7 @@ type AppIface interface { RemoveSamlPublicCertificate() *model.AppError RemoveTeamIcon(teamID string) *model.AppError RemoveTeamMemberFromTeam(teamMember *model.TeamMember, requestorId string) *model.AppError + RemoveTeamsFromRetentionPolicy(policyID string, teamIDs []string) *model.AppError RemoveUserFromChannel(userIDToRemove string, removerUserId string, channel *model.Channel) *model.AppError RemoveUserFromTeam(teamID string, userID string, requestorId string) *model.AppError RemoveUsersFromChannelNotMemberOfTeam(remover *model.User, channel *model.Channel, team *model.Team) *model.AppError @@ -928,8 +938,8 @@ type AppIface interface { SearchGroupChannels(userID, term string) (*model.ChannelList, *model.AppError) SearchPostsInTeam(teamID string, paramsList []*model.SearchParams) (*model.PostList, *model.AppError) SearchPostsInTeamForUser(terms string, userID string, teamID string, isOrSearch bool, includeDeletedChannels bool, timeZoneOffset int, page, perPage int) (*model.PostSearchResults, *model.AppError) - SearchPrivateTeams(term string) ([]*model.Team, *model.AppError) - SearchPublicTeams(term string) ([]*model.Team, *model.AppError) + SearchPrivateTeams(searchOpts *model.TeamSearch) ([]*model.Team, *model.AppError) + SearchPublicTeams(searchOpts *model.TeamSearch) ([]*model.Team, *model.AppError) SearchUserAccessTokens(term string) ([]*model.UserAccessToken, *model.AppError) SearchUsers(props *model.UserSearch, options *model.UserSearchOptions) ([]*model.User, *model.AppError) SearchUsersInChannel(channelID string, term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError) diff --git a/app/channel.go b/app/channel.go index 1c04c0f3b8..020e1d2070 100644 --- a/app/channel.go +++ b/app/channel.go @@ -1695,9 +1695,11 @@ func (a *App) GetAllChannels(page, perPage int, opts model.ChannelSearchOpts) (* opts.ExcludeChannelNames = a.DefaultChannelNames() } storeOpts := store.ChannelSearchOpts{ - ExcludeChannelNames: opts.ExcludeChannelNames, - NotAssociatedToGroup: opts.NotAssociatedToGroup, - IncludeDeleted: opts.IncludeDeleted, + ExcludeChannelNames: opts.ExcludeChannelNames, + NotAssociatedToGroup: opts.NotAssociatedToGroup, + IncludeDeleted: opts.IncludeDeleted, + ExcludePolicyConstrained: opts.ExcludePolicyConstrained, + IncludePolicyID: opts.IncludePolicyID, } channels, err := a.Srv().Store.Channel().GetAllChannels(page*perPage, perPage, storeOpts) if err != nil { @@ -2467,17 +2469,20 @@ func (a *App) SearchAllChannels(term string, opts model.ChannelSearchOpts) (*mod opts.ExcludeChannelNames = a.DefaultChannelNames() } storeOpts := store.ChannelSearchOpts{ - ExcludeChannelNames: opts.ExcludeChannelNames, - NotAssociatedToGroup: opts.NotAssociatedToGroup, - IncludeDeleted: opts.IncludeDeleted, - Deleted: opts.Deleted, - TeamIds: opts.TeamIds, - GroupConstrained: opts.GroupConstrained, - ExcludeGroupConstrained: opts.ExcludeGroupConstrained, - Public: opts.Public, - Private: opts.Private, - Page: opts.Page, - PerPage: opts.PerPage, + ExcludeChannelNames: opts.ExcludeChannelNames, + NotAssociatedToGroup: opts.NotAssociatedToGroup, + IncludeDeleted: opts.IncludeDeleted, + Deleted: opts.Deleted, + TeamIds: opts.TeamIds, + GroupConstrained: opts.GroupConstrained, + ExcludeGroupConstrained: opts.ExcludeGroupConstrained, + PolicyID: opts.PolicyID, + IncludePolicyID: opts.IncludePolicyID, + ExcludePolicyConstrained: opts.ExcludePolicyConstrained, + Public: opts.Public, + Private: opts.Private, + Page: opts.Page, + PerPage: opts.PerPage, } term = strings.TrimSpace(term) diff --git a/app/data_retention.go b/app/data_retention.go index fd29d220c3..949903e74b 100644 --- a/app/data_retention.go +++ b/app/data_retention.go @@ -9,10 +9,112 @@ import ( "github.com/mattermost/mattermost-server/v5/model" ) -func (a *App) GetDataRetentionPolicy() (*model.DataRetentionPolicy, *model.AppError) { +func (a *App) GetGlobalRetentionPolicy() (*model.GlobalRetentionPolicy, *model.AppError) { if a.DataRetention() == nil { - return nil, model.NewAppError("App.GetDataRetentionPolicy", "ent.data_retention.generic.license.error", nil, "", http.StatusNotImplemented) + return nil, newLicenseError("GetGlobalRetentionPolicy") } - - return a.DataRetention().GetPolicy() + return a.DataRetention().GetGlobalPolicy() +} + +func (a *App) GetRetentionPolicies(offset, limit int) (*model.RetentionPolicyWithTeamAndChannelCountsList, *model.AppError) { + if a.DataRetention() == nil { + return nil, newLicenseError("GetRetentionPolicies") + } + return a.DataRetention().GetPolicies(offset, limit) +} + +func (a *App) GetRetentionPoliciesCount() (int64, *model.AppError) { + if a.DataRetention() == nil { + return 0, newLicenseError("GetRetentionPoliciesCount") + } + return a.DataRetention().GetPoliciesCount() +} + +func (a *App) GetRetentionPolicy(policyID string) (*model.RetentionPolicyWithTeamAndChannelCounts, *model.AppError) { + if a.DataRetention() == nil { + return nil, newLicenseError("GetRetentionPolicy") + } + return a.DataRetention().GetPolicy(policyID) +} + +func (a *App) CreateRetentionPolicy(policy *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, *model.AppError) { + if a.DataRetention() == nil { + return nil, newLicenseError("CreateRetentionPolicy") + } + return a.DataRetention().CreatePolicy(policy) +} + +func (a *App) PatchRetentionPolicy(patch *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, *model.AppError) { + if a.DataRetention() == nil { + return nil, newLicenseError("PatchRetentionPolicy") + } + return a.DataRetention().PatchPolicy(patch) +} + +func (a *App) DeleteRetentionPolicy(policyID string) *model.AppError { + if a.DataRetention() == nil { + return newLicenseError("DeleteRetentionPolicy") + } + return a.DataRetention().DeletePolicy(policyID) +} + +func (a *App) GetTeamsForRetentionPolicy(policyID string, offset, limit int) (*model.TeamsWithCount, *model.AppError) { + if a.DataRetention() == nil { + return nil, newLicenseError("GetTeamsForRetentionPolicy") + } + return a.DataRetention().GetTeamsForPolicy(policyID, offset, limit) +} + +func (a *App) AddTeamsToRetentionPolicy(policyID string, teamIDs []string) *model.AppError { + if a.DataRetention() == nil { + return newLicenseError("AddTeamsToRetentionPolicy") + } + return a.DataRetention().AddTeamsToPolicy(policyID, teamIDs) +} + +func (a *App) RemoveTeamsFromRetentionPolicy(policyID string, teamIDs []string) *model.AppError { + if a.DataRetention() == nil { + return newLicenseError("RemoveTeamsFromRetentionPolicy") + } + return a.DataRetention().RemoveTeamsFromPolicy(policyID, teamIDs) +} + +func (a *App) GetChannelsForRetentionPolicy(policyID string, offset, limit int) (*model.ChannelsWithCount, *model.AppError) { + if a.DataRetention() == nil { + return nil, newLicenseError("GetChannelsForRetentionPolicy") + } + return a.DataRetention().GetChannelsForPolicy(policyID, offset, limit) +} + +func (a *App) AddChannelsToRetentionPolicy(policyID string, channelIDs []string) *model.AppError { + if a.DataRetention() == nil { + return newLicenseError("AddChannelsToRetentionPolicies") + } + return a.DataRetention().AddChannelsToPolicy(policyID, channelIDs) +} + +func (a *App) RemoveChannelsFromRetentionPolicy(policyID string, channelIDs []string) *model.AppError { + if a.DataRetention() == nil { + return newLicenseError("RemoveChannelsFromRetentionPolicy") + } + return a.DataRetention().RemoveChannelsFromPolicy(policyID, channelIDs) +} + +func (a *App) GetTeamPoliciesForUser(userID string, offset, limit int) (*model.RetentionPolicyForTeamList, *model.AppError) { + if a.DataRetention() == nil { + return nil, newLicenseError("GetTeamPoliciesForUser") + } + return a.DataRetention().GetTeamPoliciesForUser(userID, offset, limit) +} + +func (a *App) GetChannelPoliciesForUser(userID string, offset, limit int) (*model.RetentionPolicyForChannelList, *model.AppError) { + if a.DataRetention() == nil { + return nil, newLicenseError("GetChannelPoliciesForUser") + } + return a.DataRetention().GetChannelPoliciesForUser(userID, offset, limit) +} + +func newLicenseError(methodName string) *model.AppError { + return model.NewAppError("App."+methodName, "ent.data_retention.generic.license.error", + nil, "", http.StatusNotImplemented) } diff --git a/app/helper_test.go b/app/helper_test.go index 28ee5c06df..11f1060a8c 100644 --- a/app/helper_test.go +++ b/app/helper_test.go @@ -606,7 +606,7 @@ func (*TestHelper) ResetEmojisMigration() { } func (th *TestHelper) CheckTeamCount(t *testing.T, expected int64) { - teamCount, err := th.App.Srv().Store.Team().AnalyticsTeamCount(false) + teamCount, err := th.App.Srv().Store.Team().AnalyticsTeamCount(nil) require.NoError(t, err, "Failed to get team count.") require.Equalf(t, teamCount, expected, "Unexpected number of teams. Expected: %v, found: %v", expected, teamCount) } diff --git a/app/import_functions_test.go b/app/import_functions_test.go index a2d4c2ab0c..6f9a87114f 100644 --- a/app/import_functions_test.go +++ b/app/import_functions_test.go @@ -505,7 +505,7 @@ func TestImportImportTeam(t *testing.T) { scheme2 := th.SetupTeamScheme() // Check how many teams are in the database. - teamsCount, err := th.App.Srv().Store.Team().AnalyticsTeamCount(false) + teamsCount, err := th.App.Srv().Store.Team().AnalyticsTeamCount(nil) require.NoError(t, err, "Failed to get team count.") data := TeamImportData{ diff --git a/app/layer_generators/main.go b/app/layer_generators/main.go index 5ac8b8ac48..4ed14b50c0 100644 --- a/app/layer_generators/main.go +++ b/app/layer_generators/main.go @@ -26,7 +26,7 @@ var ( outputFile string inputFile string outputFileTemplate string - basicTypes = map[string]bool{"int": true, "string": true, "float": true, "bool": true, "byte": true, "int64": true, "error": true} + basicTypes = map[string]bool{"int": true, "uint": true, "string": true, "float": true, "bool": true, "byte": true, "int64": true, "uint64": true, "error": true} textRegexp = regexp.MustCompile(`\w+$`) ) diff --git a/app/opentracing/opentracing_layer.go b/app/opentracing/opentracing_layer.go index cff0d70601..40b590c468 100644 --- a/app/opentracing/opentracing_layer.go +++ b/app/opentracing/opentracing_layer.go @@ -117,6 +117,28 @@ func (a *OpenTracingAppLayer) AddChannelMember(userID string, channel *model.Cha return resultVar0, resultVar1 } +func (a *OpenTracingAppLayer) AddChannelsToRetentionPolicy(policyID string, channelIDs []string) *model.AppError { + origCtx := a.ctx + span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.AddChannelsToRetentionPolicy") + + a.ctx = newCtx + a.app.Srv().Store.SetContext(newCtx) + defer func() { + a.app.Srv().Store.SetContext(origCtx) + a.ctx = origCtx + }() + + defer span.Finish() + resultVar0 := a.app.AddChannelsToRetentionPolicy(policyID, channelIDs) + + if resultVar0 != nil { + span.LogFields(spanlog.Error(resultVar0)) + ext.Error.Set(span, true) + } + + return resultVar0 +} + func (a *OpenTracingAppLayer) AddConfigListener(listener func(*model.Config, *model.Config)) string { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.AddConfigListener") @@ -458,6 +480,28 @@ func (a *OpenTracingAppLayer) AddTeamMembers(teamID string, userIDs []string, us return resultVar0, resultVar1 } +func (a *OpenTracingAppLayer) AddTeamsToRetentionPolicy(policyID string, teamIDs []string) *model.AppError { + origCtx := a.ctx + span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.AddTeamsToRetentionPolicy") + + a.ctx = newCtx + a.app.Srv().Store.SetContext(newCtx) + defer func() { + a.app.Srv().Store.SetContext(origCtx) + a.ctx = origCtx + }() + + defer span.Finish() + resultVar0 := a.app.AddTeamsToRetentionPolicy(policyID, teamIDs) + + if resultVar0 != nil { + span.LogFields(spanlog.Error(resultVar0)) + ext.Error.Set(span, true) + } + + return resultVar0 +} + func (a *OpenTracingAppLayer) AddUserToChannel(user *model.User, channel *model.Channel, skipTeamMemberIntegrityCheck bool) (*model.ChannelMember, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.AddUserToChannel") @@ -2148,6 +2192,28 @@ func (a *OpenTracingAppLayer) CreatePostMissingChannel(post *model.Post, trigger return resultVar0, resultVar1 } +func (a *OpenTracingAppLayer) CreateRetentionPolicy(policy *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, *model.AppError) { + origCtx := a.ctx + span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.CreateRetentionPolicy") + + a.ctx = newCtx + a.app.Srv().Store.SetContext(newCtx) + defer func() { + a.app.Srv().Store.SetContext(origCtx) + a.ctx = origCtx + }() + + defer span.Finish() + resultVar0, resultVar1 := a.app.CreateRetentionPolicy(policy) + + if resultVar1 != nil { + span.LogFields(spanlog.Error(resultVar1)) + ext.Error.Set(span, true) + } + + return resultVar0, resultVar1 +} + func (a *OpenTracingAppLayer) CreateRole(role *model.Role) (*model.Role, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.CreateRole") @@ -3156,6 +3222,28 @@ func (a *OpenTracingAppLayer) DeleteRemoteCluster(remoteClusterId string) (bool, return resultVar0, resultVar1 } +func (a *OpenTracingAppLayer) DeleteRetentionPolicy(policyID string) *model.AppError { + origCtx := a.ctx + span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.DeleteRetentionPolicy") + + a.ctx = newCtx + a.app.Srv().Store.SetContext(newCtx) + defer func() { + a.app.Srv().Store.SetContext(origCtx) + a.ctx = origCtx + }() + + defer span.Finish() + resultVar0 := a.app.DeleteRetentionPolicy(policyID) + + if resultVar0 != nil { + span.LogFields(spanlog.Error(resultVar0)) + ext.Error.Set(span, true) + } + + return resultVar0 +} + func (a *OpenTracingAppLayer) DeleteScheme(schemeId string) (*model.Scheme, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.DeleteScheme") @@ -4264,50 +4352,6 @@ func (a *OpenTracingAppLayer) GetAllPrivateTeams() ([]*model.Team, *model.AppErr return resultVar0, resultVar1 } -func (a *OpenTracingAppLayer) GetAllPrivateTeamsPage(offset int, limit int) ([]*model.Team, *model.AppError) { - origCtx := a.ctx - span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetAllPrivateTeamsPage") - - a.ctx = newCtx - a.app.Srv().Store.SetContext(newCtx) - defer func() { - a.app.Srv().Store.SetContext(origCtx) - a.ctx = origCtx - }() - - defer span.Finish() - resultVar0, resultVar1 := a.app.GetAllPrivateTeamsPage(offset, limit) - - if resultVar1 != nil { - span.LogFields(spanlog.Error(resultVar1)) - ext.Error.Set(span, true) - } - - return resultVar0, resultVar1 -} - -func (a *OpenTracingAppLayer) GetAllPrivateTeamsPageWithCount(offset int, limit int) (*model.TeamsWithCount, *model.AppError) { - origCtx := a.ctx - span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetAllPrivateTeamsPageWithCount") - - a.ctx = newCtx - a.app.Srv().Store.SetContext(newCtx) - defer func() { - a.app.Srv().Store.SetContext(origCtx) - a.ctx = origCtx - }() - - defer span.Finish() - resultVar0, resultVar1 := a.app.GetAllPrivateTeamsPageWithCount(offset, limit) - - if resultVar1 != nil { - span.LogFields(spanlog.Error(resultVar1)) - ext.Error.Set(span, true) - } - - return resultVar0, resultVar1 -} - func (a *OpenTracingAppLayer) GetAllPublicTeams() ([]*model.Team, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetAllPublicTeams") @@ -4330,50 +4374,6 @@ func (a *OpenTracingAppLayer) GetAllPublicTeams() ([]*model.Team, *model.AppErro return resultVar0, resultVar1 } -func (a *OpenTracingAppLayer) GetAllPublicTeamsPage(offset int, limit int) ([]*model.Team, *model.AppError) { - origCtx := a.ctx - span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetAllPublicTeamsPage") - - a.ctx = newCtx - a.app.Srv().Store.SetContext(newCtx) - defer func() { - a.app.Srv().Store.SetContext(origCtx) - a.ctx = origCtx - }() - - defer span.Finish() - resultVar0, resultVar1 := a.app.GetAllPublicTeamsPage(offset, limit) - - if resultVar1 != nil { - span.LogFields(spanlog.Error(resultVar1)) - ext.Error.Set(span, true) - } - - return resultVar0, resultVar1 -} - -func (a *OpenTracingAppLayer) GetAllPublicTeamsPageWithCount(offset int, limit int) (*model.TeamsWithCount, *model.AppError) { - origCtx := a.ctx - span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetAllPublicTeamsPageWithCount") - - a.ctx = newCtx - a.app.Srv().Store.SetContext(newCtx) - defer func() { - a.app.Srv().Store.SetContext(origCtx) - a.ctx = origCtx - }() - - defer span.Finish() - resultVar0, resultVar1 := a.app.GetAllPublicTeamsPageWithCount(offset, limit) - - if resultVar1 != nil { - span.LogFields(spanlog.Error(resultVar1)) - ext.Error.Set(span, true) - } - - return resultVar0, resultVar1 -} - func (a *OpenTracingAppLayer) GetAllRemoteClusters(filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetAllRemoteClusters") @@ -4457,7 +4457,7 @@ func (a *OpenTracingAppLayer) GetAllTeams() ([]*model.Team, *model.AppError) { return resultVar0, resultVar1 } -func (a *OpenTracingAppLayer) GetAllTeamsPage(offset int, limit int) ([]*model.Team, *model.AppError) { +func (a *OpenTracingAppLayer) GetAllTeamsPage(offset int, limit int, opts *model.TeamSearch) ([]*model.Team, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetAllTeamsPage") @@ -4469,7 +4469,7 @@ func (a *OpenTracingAppLayer) GetAllTeamsPage(offset int, limit int) ([]*model.T }() defer span.Finish() - resultVar0, resultVar1 := a.app.GetAllTeamsPage(offset, limit) + resultVar0, resultVar1 := a.app.GetAllTeamsPage(offset, limit, opts) if resultVar1 != nil { span.LogFields(spanlog.Error(resultVar1)) @@ -4479,7 +4479,7 @@ func (a *OpenTracingAppLayer) GetAllTeamsPage(offset int, limit int) ([]*model.T return resultVar0, resultVar1 } -func (a *OpenTracingAppLayer) GetAllTeamsPageWithCount(offset int, limit int) (*model.TeamsWithCount, *model.AppError) { +func (a *OpenTracingAppLayer) GetAllTeamsPageWithCount(offset int, limit int, opts *model.TeamSearch) (*model.TeamsWithCount, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetAllTeamsPageWithCount") @@ -4491,7 +4491,7 @@ func (a *OpenTracingAppLayer) GetAllTeamsPageWithCount(offset int, limit int) (* }() defer span.Finish() - resultVar0, resultVar1 := a.app.GetAllTeamsPageWithCount(offset, limit) + resultVar0, resultVar1 := a.app.GetAllTeamsPageWithCount(offset, limit, opts) if resultVar1 != nil { span.LogFields(spanlog.Error(resultVar1)) @@ -5051,6 +5051,28 @@ func (a *OpenTracingAppLayer) GetChannelPinnedPostCount(channelID string) (int64 return resultVar0, resultVar1 } +func (a *OpenTracingAppLayer) GetChannelPoliciesForUser(userID string, offset int, limit int) (*model.RetentionPolicyForChannelList, *model.AppError) { + origCtx := a.ctx + span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetChannelPoliciesForUser") + + a.ctx = newCtx + a.app.Srv().Store.SetContext(newCtx) + defer func() { + a.app.Srv().Store.SetContext(origCtx) + a.ctx = origCtx + }() + + defer span.Finish() + resultVar0, resultVar1 := a.app.GetChannelPoliciesForUser(userID, offset, limit) + + if resultVar1 != nil { + span.LogFields(spanlog.Error(resultVar1)) + ext.Error.Set(span, true) + } + + return resultVar0, resultVar1 +} + func (a *OpenTracingAppLayer) GetChannelUnread(channelID string, userID string) (*model.ChannelUnread, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetChannelUnread") @@ -5095,6 +5117,28 @@ func (a *OpenTracingAppLayer) GetChannelsByNames(channelNames []string, teamID s return resultVar0, resultVar1 } +func (a *OpenTracingAppLayer) GetChannelsForRetentionPolicy(policyID string, offset int, limit int) (*model.ChannelsWithCount, *model.AppError) { + origCtx := a.ctx + span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetChannelsForRetentionPolicy") + + a.ctx = newCtx + a.app.Srv().Store.SetContext(newCtx) + defer func() { + a.app.Srv().Store.SetContext(origCtx) + a.ctx = origCtx + }() + + defer span.Finish() + resultVar0, resultVar1 := a.app.GetChannelsForRetentionPolicy(policyID, offset, limit) + + if resultVar1 != nil { + span.LogFields(spanlog.Error(resultVar1)) + ext.Error.Set(span, true) + } + + return resultVar0, resultVar1 +} + func (a *OpenTracingAppLayer) GetChannelsForScheme(scheme *model.Scheme, offset int, limit int) (model.ChannelList, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetChannelsForScheme") @@ -5388,28 +5432,6 @@ func (a *OpenTracingAppLayer) GetCookieDomain() string { return resultVar0 } -func (a *OpenTracingAppLayer) GetDataRetentionPolicy() (*model.DataRetentionPolicy, *model.AppError) { - origCtx := a.ctx - span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetDataRetentionPolicy") - - a.ctx = newCtx - a.app.Srv().Store.SetContext(newCtx) - defer func() { - a.app.Srv().Store.SetContext(origCtx) - a.ctx = origCtx - }() - - defer span.Finish() - resultVar0, resultVar1 := a.app.GetDataRetentionPolicy() - - if resultVar1 != nil { - span.LogFields(spanlog.Error(resultVar1)) - ext.Error.Set(span, true) - } - - return resultVar0, resultVar1 -} - func (a *OpenTracingAppLayer) GetDefaultProfileImage(user *model.User) ([]byte, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetDefaultProfileImage") @@ -5801,6 +5823,28 @@ func (a *OpenTracingAppLayer) GetFlaggedPostsForTeam(userID string, teamID strin return resultVar0, resultVar1 } +func (a *OpenTracingAppLayer) GetGlobalRetentionPolicy() (*model.GlobalRetentionPolicy, *model.AppError) { + origCtx := a.ctx + span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetGlobalRetentionPolicy") + + a.ctx = newCtx + a.app.Srv().Store.SetContext(newCtx) + defer func() { + a.app.Srv().Store.SetContext(origCtx) + a.ctx = origCtx + }() + + defer span.Finish() + resultVar0, resultVar1 := a.app.GetGlobalRetentionPolicy() + + if resultVar1 != nil { + span.LogFields(spanlog.Error(resultVar1)) + ext.Error.Set(span, true) + } + + return resultVar0, resultVar1 +} + func (a *OpenTracingAppLayer) GetGroup(id string) (*model.Group, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetGroup") @@ -7917,6 +7961,72 @@ func (a *OpenTracingAppLayer) GetRemoteClusterSession(token string, remoteId str return resultVar0, resultVar1 } +func (a *OpenTracingAppLayer) GetRetentionPolicies(offset int, limit int) (*model.RetentionPolicyWithTeamAndChannelCountsList, *model.AppError) { + origCtx := a.ctx + span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetRetentionPolicies") + + a.ctx = newCtx + a.app.Srv().Store.SetContext(newCtx) + defer func() { + a.app.Srv().Store.SetContext(origCtx) + a.ctx = origCtx + }() + + defer span.Finish() + resultVar0, resultVar1 := a.app.GetRetentionPolicies(offset, limit) + + if resultVar1 != nil { + span.LogFields(spanlog.Error(resultVar1)) + ext.Error.Set(span, true) + } + + return resultVar0, resultVar1 +} + +func (a *OpenTracingAppLayer) GetRetentionPoliciesCount() (int64, *model.AppError) { + origCtx := a.ctx + span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetRetentionPoliciesCount") + + a.ctx = newCtx + a.app.Srv().Store.SetContext(newCtx) + defer func() { + a.app.Srv().Store.SetContext(origCtx) + a.ctx = origCtx + }() + + defer span.Finish() + resultVar0, resultVar1 := a.app.GetRetentionPoliciesCount() + + if resultVar1 != nil { + span.LogFields(spanlog.Error(resultVar1)) + ext.Error.Set(span, true) + } + + return resultVar0, resultVar1 +} + +func (a *OpenTracingAppLayer) GetRetentionPolicy(policyID string) (*model.RetentionPolicyWithTeamAndChannelCounts, *model.AppError) { + origCtx := a.ctx + span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetRetentionPolicy") + + a.ctx = newCtx + a.app.Srv().Store.SetContext(newCtx) + defer func() { + a.app.Srv().Store.SetContext(origCtx) + a.ctx = origCtx + }() + + defer span.Finish() + resultVar0, resultVar1 := a.app.GetRetentionPolicy(policyID) + + if resultVar1 != nil { + span.LogFields(spanlog.Error(resultVar1)) + ext.Error.Set(span, true) + } + + return resultVar0, resultVar1 +} + func (a *OpenTracingAppLayer) GetRole(id string) (*model.Role, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetRole") @@ -8872,6 +8982,28 @@ func (a *OpenTracingAppLayer) GetTeamMembersForUserWithPagination(userID string, return resultVar0, resultVar1 } +func (a *OpenTracingAppLayer) GetTeamPoliciesForUser(userID string, offset int, limit int) (*model.RetentionPolicyForTeamList, *model.AppError) { + origCtx := a.ctx + span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetTeamPoliciesForUser") + + a.ctx = newCtx + a.app.Srv().Store.SetContext(newCtx) + defer func() { + a.app.Srv().Store.SetContext(origCtx) + a.ctx = origCtx + }() + + defer span.Finish() + resultVar0, resultVar1 := a.app.GetTeamPoliciesForUser(userID, offset, limit) + + if resultVar1 != nil { + span.LogFields(spanlog.Error(resultVar1)) + ext.Error.Set(span, true) + } + + return resultVar0, resultVar1 +} + func (a *OpenTracingAppLayer) GetTeamSchemeChannelRoles(teamID string) (guestRoleName string, userRoleName string, adminRoleName string, err *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetTeamSchemeChannelRoles") @@ -8938,6 +9070,28 @@ func (a *OpenTracingAppLayer) GetTeamUnread(teamID string, userID string) (*mode return resultVar0, resultVar1 } +func (a *OpenTracingAppLayer) GetTeamsForRetentionPolicy(policyID string, offset int, limit int) (*model.TeamsWithCount, *model.AppError) { + origCtx := a.ctx + span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetTeamsForRetentionPolicy") + + a.ctx = newCtx + a.app.Srv().Store.SetContext(newCtx) + defer func() { + a.app.Srv().Store.SetContext(origCtx) + a.ctx = origCtx + }() + + defer span.Finish() + resultVar0, resultVar1 := a.app.GetTeamsForRetentionPolicy(policyID, offset, limit) + + if resultVar1 != nil { + span.LogFields(spanlog.Error(resultVar1)) + ext.Error.Set(span, true) + } + + return resultVar0, resultVar1 +} + func (a *OpenTracingAppLayer) GetTeamsForScheme(scheme *model.Scheme, offset int, limit int) ([]*model.Team, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetTeamsForScheme") @@ -11558,6 +11712,28 @@ func (a *OpenTracingAppLayer) PatchPost(postID string, patch *model.PostPatch) ( return resultVar0, resultVar1 } +func (a *OpenTracingAppLayer) PatchRetentionPolicy(patch *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, *model.AppError) { + origCtx := a.ctx + span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.PatchRetentionPolicy") + + a.ctx = newCtx + a.app.Srv().Store.SetContext(newCtx) + defer func() { + a.app.Srv().Store.SetContext(origCtx) + a.ctx = origCtx + }() + + defer span.Finish() + resultVar0, resultVar1 := a.app.PatchRetentionPolicy(patch) + + if resultVar1 != nil { + span.LogFields(spanlog.Error(resultVar1)) + ext.Error.Set(span, true) + } + + return resultVar0, resultVar1 +} + func (a *OpenTracingAppLayer) PatchRole(role *model.Role, patch *model.RolePatch) (*model.Role, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.PatchRole") @@ -12330,6 +12506,28 @@ func (a *OpenTracingAppLayer) RemoveAllDeactivatedMembersFromChannel(channel *mo return resultVar0 } +func (a *OpenTracingAppLayer) RemoveChannelsFromRetentionPolicy(policyID string, channelIDs []string) *model.AppError { + origCtx := a.ctx + span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.RemoveChannelsFromRetentionPolicy") + + a.ctx = newCtx + a.app.Srv().Store.SetContext(newCtx) + defer func() { + a.app.Srv().Store.SetContext(origCtx) + a.ctx = origCtx + }() + + defer span.Finish() + resultVar0 := a.app.RemoveChannelsFromRetentionPolicy(policyID, channelIDs) + + if resultVar0 != nil { + span.LogFields(spanlog.Error(resultVar0)) + ext.Error.Set(span, true) + } + + return resultVar0 +} + func (a *OpenTracingAppLayer) RemoveConfigListener(id string) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.RemoveConfigListener") @@ -12624,6 +12822,28 @@ func (a *OpenTracingAppLayer) RemoveTeamMemberFromTeam(teamMember *model.TeamMem return resultVar0 } +func (a *OpenTracingAppLayer) RemoveTeamsFromRetentionPolicy(policyID string, teamIDs []string) *model.AppError { + origCtx := a.ctx + span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.RemoveTeamsFromRetentionPolicy") + + a.ctx = newCtx + a.app.Srv().Store.SetContext(newCtx) + defer func() { + a.app.Srv().Store.SetContext(origCtx) + a.ctx = origCtx + }() + + defer span.Finish() + resultVar0 := a.app.RemoveTeamsFromRetentionPolicy(policyID, teamIDs) + + if resultVar0 != nil { + span.LogFields(spanlog.Error(resultVar0)) + ext.Error.Set(span, true) + } + + return resultVar0 +} + func (a *OpenTracingAppLayer) RemoveUserFromChannel(userIDToRemove string, removerUserId string, channel *model.Channel) *model.AppError { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.RemoveUserFromChannel") @@ -13575,7 +13795,7 @@ func (a *OpenTracingAppLayer) SearchPostsInTeamForUser(terms string, userID stri return resultVar0, resultVar1 } -func (a *OpenTracingAppLayer) SearchPrivateTeams(term string) ([]*model.Team, *model.AppError) { +func (a *OpenTracingAppLayer) SearchPrivateTeams(searchOpts *model.TeamSearch) ([]*model.Team, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SearchPrivateTeams") @@ -13587,7 +13807,7 @@ func (a *OpenTracingAppLayer) SearchPrivateTeams(term string) ([]*model.Team, *m }() defer span.Finish() - resultVar0, resultVar1 := a.app.SearchPrivateTeams(term) + resultVar0, resultVar1 := a.app.SearchPrivateTeams(searchOpts) if resultVar1 != nil { span.LogFields(spanlog.Error(resultVar1)) @@ -13597,7 +13817,7 @@ func (a *OpenTracingAppLayer) SearchPrivateTeams(term string) ([]*model.Team, *m return resultVar0, resultVar1 } -func (a *OpenTracingAppLayer) SearchPublicTeams(term string) ([]*model.Team, *model.AppError) { +func (a *OpenTracingAppLayer) SearchPublicTeams(searchOpts *model.TeamSearch) ([]*model.Team, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SearchPublicTeams") @@ -13609,7 +13829,7 @@ func (a *OpenTracingAppLayer) SearchPublicTeams(term string) ([]*model.Team, *mo }() defer span.Finish() - resultVar0, resultVar1 := a.app.SearchPublicTeams(term) + resultVar0, resultVar1 := a.app.SearchPublicTeams(searchOpts) if resultVar1 != nil { span.LogFields(spanlog.Error(resultVar1)) diff --git a/app/security_update_check.go b/app/security_update_check.go index 7e84cdc43a..ad0c226a72 100644 --- a/app/security_update_check.go +++ b/app/security_update_check.go @@ -76,7 +76,7 @@ func (s *Server) DoSecurityUpdateCheck() { v.Set(PropSecurityActiveUserCount, strconv.FormatInt(ucr, 10)) } - if teamCount, err := s.Store.Team().AnalyticsTeamCount(false); err == nil { + if teamCount, err := s.Store.Team().AnalyticsTeamCount(nil); err == nil { v.Set(PropSecurityTeamCount, strconv.FormatInt(teamCount, 10)) } diff --git a/app/server.go b/app/server.go index 79fe4b888a..d573e3f2aa 100644 --- a/app/server.go +++ b/app/server.go @@ -1488,7 +1488,7 @@ func doCheckWarnMetricStatus(a *App) { mlog.Debug("Error attempting to get active registered users.", mlog.Err(err0)) } - teamCount, err1 := a.Srv().Store.Team().AnalyticsTeamCount(false) + teamCount, err1 := a.Srv().Store.Team().AnalyticsTeamCount(nil) if err1 != nil { mlog.Debug("Error attempting to get number of teams.", mlog.Err(err1)) } diff --git a/app/team.go b/app/team.go index 92b7cd6f45..653d0d64c2 100644 --- a/app/team.go +++ b/app/team.go @@ -879,8 +879,8 @@ func (a *App) GetAllTeams() ([]*model.Team, *model.AppError) { return teams, nil } -func (a *App) GetAllTeamsPage(offset int, limit int) ([]*model.Team, *model.AppError) { - teams, err := a.Srv().Store.Team().GetAllPage(offset, limit) +func (a *App) GetAllTeamsPage(offset int, limit int, opts *model.TeamSearch) ([]*model.Team, *model.AppError) { + teams, err := a.Srv().Store.Team().GetAllPage(offset, limit, opts) if err != nil { return nil, model.NewAppError("GetAllTeamsPage", "app.team.get_all.app_error", nil, err.Error(), http.StatusInternalServerError) } @@ -888,12 +888,12 @@ func (a *App) GetAllTeamsPage(offset int, limit int) ([]*model.Team, *model.AppE return teams, nil } -func (a *App) GetAllTeamsPageWithCount(offset int, limit int) (*model.TeamsWithCount, *model.AppError) { - totalCount, err := a.Srv().Store.Team().AnalyticsTeamCount(true) +func (a *App) GetAllTeamsPageWithCount(offset int, limit int, opts *model.TeamSearch) (*model.TeamsWithCount, *model.AppError) { + totalCount, err := a.Srv().Store.Team().AnalyticsTeamCount(opts) if err != nil { return nil, model.NewAppError("GetAllTeamsPageWithCount", "app.team.analytics_team_count.app_error", nil, err.Error(), http.StatusInternalServerError) } - teams, err := a.Srv().Store.Team().GetAllPage(offset, limit) + teams, err := a.Srv().Store.Team().GetAllPage(offset, limit, opts) if err != nil { return nil, model.NewAppError("GetAllTeamsPageWithCount", "app.team.get_all.app_error", nil, err.Error(), http.StatusInternalServerError) } @@ -909,27 +909,6 @@ func (a *App) GetAllPrivateTeams() ([]*model.Team, *model.AppError) { return teams, nil } -func (a *App) GetAllPrivateTeamsPage(offset int, limit int) ([]*model.Team, *model.AppError) { - teams, err := a.Srv().Store.Team().GetAllPrivateTeamPageListing(offset, limit) - if err != nil { - return nil, model.NewAppError("GetAllPrivateTeamsPage", "app.team.get_all_private_team_page_listing.app_error", nil, err.Error(), http.StatusInternalServerError) - } - - return teams, nil -} - -func (a *App) GetAllPrivateTeamsPageWithCount(offset int, limit int) (*model.TeamsWithCount, *model.AppError) { - totalCount, err := a.Srv().Store.Team().AnalyticsPrivateTeamCount() - if err != nil { - return nil, model.NewAppError("GetAllPrivateTeamsPageWithCount", "app.team.analytics_private_team_count.app_error", nil, err.Error(), http.StatusInternalServerError) - } - teams, err := a.Srv().Store.Team().GetAllPrivateTeamPageListing(offset, limit) - if err != nil { - return nil, model.NewAppError("GetAllPrivateTeamsPageWithCount", "app.team.get_all_private_team_page_listing.app_error", nil, err.Error(), http.StatusInternalServerError) - } - return &model.TeamsWithCount{Teams: teams, TotalCount: totalCount}, nil -} - func (a *App) GetAllPublicTeams() ([]*model.Team, *model.AppError) { teams, err := a.Srv().Store.Team().GetAllTeamListing() if err != nil { @@ -939,46 +918,25 @@ func (a *App) GetAllPublicTeams() ([]*model.Team, *model.AppError) { return teams, nil } -func (a *App) GetAllPublicTeamsPage(offset int, limit int) ([]*model.Team, *model.AppError) { - teams, err := a.Srv().Store.Team().GetAllTeamPageListing(offset, limit) - if err != nil { - return nil, model.NewAppError("GetAllPublicTeamsPage", "app.team.get_all_team_listing.app_error", nil, err.Error(), http.StatusInternalServerError) - } - - return teams, nil -} - -func (a *App) GetAllPublicTeamsPageWithCount(offset int, limit int) (*model.TeamsWithCount, *model.AppError) { - totalCount, err := a.Srv().Store.Team().AnalyticsPublicTeamCount() - if err != nil { - return nil, model.NewAppError("GetAllPublicTeamsPageWithCount", "app.team.analytics_public_team_count.app_error", nil, err.Error(), http.StatusInternalServerError) - } - teams, err := a.Srv().Store.Team().GetAllPublicTeamPageListing(offset, limit) - if err != nil { - return nil, model.NewAppError("GetAllPublicTeamsPageWithCount", "app.team.get_all_private_team_listing.app_error", nil, err.Error(), http.StatusInternalServerError) - } - return &model.TeamsWithCount{Teams: teams, TotalCount: totalCount}, nil -} - // SearchAllTeams returns a team list and the total count of the results func (a *App) SearchAllTeams(searchOpts *model.TeamSearch) ([]*model.Team, int64, *model.AppError) { if searchOpts.IsPaginated() { - teams, count, err := a.Srv().Store.Team().SearchAllPaged(searchOpts.Term, searchOpts) + teams, count, err := a.Srv().Store.Team().SearchAllPaged(searchOpts) if err != nil { return nil, 0, model.NewAppError("SearchAllTeams", "app.team.search_all_team.app_error", nil, err.Error(), http.StatusInternalServerError) } return teams, count, nil } - results, err := a.Srv().Store.Team().SearchAll(searchOpts.Term, searchOpts) + results, err := a.Srv().Store.Team().SearchAll(searchOpts) if err != nil { return nil, 0, model.NewAppError("SearchAllTeams", "app.team.search_all_team.app_error", nil, err.Error(), http.StatusInternalServerError) } return results, int64(len(results)), nil } -func (a *App) SearchPublicTeams(term string) ([]*model.Team, *model.AppError) { - teams, err := a.Srv().Store.Team().SearchOpen(term) +func (a *App) SearchPublicTeams(searchOpts *model.TeamSearch) ([]*model.Team, *model.AppError) { + teams, err := a.Srv().Store.Team().SearchOpen(searchOpts) if err != nil { return nil, model.NewAppError("SearchPublicTeams", "app.team.search_open_team.app_error", nil, err.Error(), http.StatusInternalServerError) } @@ -986,8 +944,8 @@ func (a *App) SearchPublicTeams(term string) ([]*model.Team, *model.AppError) { return teams, nil } -func (a *App) SearchPrivateTeams(term string) ([]*model.Team, *model.AppError) { - teams, err := a.Srv().Store.Team().SearchPrivate(term) +func (a *App) SearchPrivateTeams(searchOpts *model.TeamSearch) ([]*model.Team, *model.AppError) { + teams, err := a.Srv().Store.Team().SearchPrivate(searchOpts) if err != nil { return nil, model.NewAppError("SearchPrivateTeams", "app.team.search_private_team.app_error", nil, err.Error(), http.StatusInternalServerError) } diff --git a/einterfaces/data_retention.go b/einterfaces/data_retention.go index 99a1dd2ba7..4d75a0af27 100644 --- a/einterfaces/data_retention.go +++ b/einterfaces/data_retention.go @@ -8,5 +8,19 @@ import ( ) type DataRetentionInterface interface { - GetPolicy() (*model.DataRetentionPolicy, *model.AppError) + GetGlobalPolicy() (*model.GlobalRetentionPolicy, *model.AppError) + GetPolicies(offset, limit int) (*model.RetentionPolicyWithTeamAndChannelCountsList, *model.AppError) + GetPoliciesCount() (int64, *model.AppError) + GetPolicy(policyID string) (*model.RetentionPolicyWithTeamAndChannelCounts, *model.AppError) + CreatePolicy(policy *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, *model.AppError) + PatchPolicy(patch *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, *model.AppError) + DeletePolicy(policyID string) *model.AppError + GetTeamsForPolicy(policyID string, offset, limit int) (*model.TeamsWithCount, *model.AppError) + AddTeamsToPolicy(policyID string, teamIDs []string) *model.AppError + RemoveTeamsFromPolicy(policyID string, teamIDs []string) *model.AppError + GetChannelsForPolicy(policyID string, offset, limit int) (*model.ChannelsWithCount, *model.AppError) + AddChannelsToPolicy(policyID string, channelIDs []string) *model.AppError + RemoveChannelsFromPolicy(policyID string, channelIDs []string) *model.AppError + GetTeamPoliciesForUser(userID string, offset, limit int) (*model.RetentionPolicyForTeamList, *model.AppError) + GetChannelPoliciesForUser(userID string, offset, limit int) (*model.RetentionPolicyForChannelList, *model.AppError) } diff --git a/einterfaces/mocks/DataRetentionInterface.go b/einterfaces/mocks/DataRetentionInterface.go index 59f1a22b6b..78bf3d247b 100644 --- a/einterfaces/mocks/DataRetentionInterface.go +++ b/einterfaces/mocks/DataRetentionInterface.go @@ -14,16 +14,139 @@ type DataRetentionInterface struct { mock.Mock } -// GetPolicy provides a mock function with given fields: -func (_m *DataRetentionInterface) GetPolicy() (*model.DataRetentionPolicy, *model.AppError) { +// AddChannelsToPolicy provides a mock function with given fields: policyID, channelIDs +func (_m *DataRetentionInterface) AddChannelsToPolicy(policyID string, channelIDs []string) *model.AppError { + ret := _m.Called(policyID, channelIDs) + + var r0 *model.AppError + if rf, ok := ret.Get(0).(func(string, []string) *model.AppError); ok { + r0 = rf(policyID, channelIDs) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.AppError) + } + } + + return r0 +} + +// AddTeamsToPolicy provides a mock function with given fields: policyID, teamIDs +func (_m *DataRetentionInterface) AddTeamsToPolicy(policyID string, teamIDs []string) *model.AppError { + ret := _m.Called(policyID, teamIDs) + + var r0 *model.AppError + if rf, ok := ret.Get(0).(func(string, []string) *model.AppError); ok { + r0 = rf(policyID, teamIDs) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.AppError) + } + } + + return r0 +} + +// CreatePolicy provides a mock function with given fields: policy +func (_m *DataRetentionInterface) CreatePolicy(policy *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, *model.AppError) { + ret := _m.Called(policy) + + var r0 *model.RetentionPolicyWithTeamAndChannelCounts + if rf, ok := ret.Get(0).(func(*model.RetentionPolicyWithTeamAndChannelIDs) *model.RetentionPolicyWithTeamAndChannelCounts); ok { + r0 = rf(policy) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.RetentionPolicyWithTeamAndChannelCounts) + } + } + + var r1 *model.AppError + if rf, ok := ret.Get(1).(func(*model.RetentionPolicyWithTeamAndChannelIDs) *model.AppError); ok { + r1 = rf(policy) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*model.AppError) + } + } + + return r0, r1 +} + +// DeletePolicy provides a mock function with given fields: policyID +func (_m *DataRetentionInterface) DeletePolicy(policyID string) *model.AppError { + ret := _m.Called(policyID) + + var r0 *model.AppError + if rf, ok := ret.Get(0).(func(string) *model.AppError); ok { + r0 = rf(policyID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.AppError) + } + } + + return r0 +} + +// GetChannelPoliciesForUser provides a mock function with given fields: userID, offset, limit +func (_m *DataRetentionInterface) GetChannelPoliciesForUser(userID string, offset int, limit int) (*model.RetentionPolicyForChannelList, *model.AppError) { + ret := _m.Called(userID, offset, limit) + + var r0 *model.RetentionPolicyForChannelList + if rf, ok := ret.Get(0).(func(string, int, int) *model.RetentionPolicyForChannelList); ok { + r0 = rf(userID, offset, limit) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.RetentionPolicyForChannelList) + } + } + + var r1 *model.AppError + if rf, ok := ret.Get(1).(func(string, int, int) *model.AppError); ok { + r1 = rf(userID, offset, limit) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*model.AppError) + } + } + + return r0, r1 +} + +// GetChannelsForPolicy provides a mock function with given fields: policyID, offset, limit +func (_m *DataRetentionInterface) GetChannelsForPolicy(policyID string, offset int, limit int) (*model.ChannelsWithCount, *model.AppError) { + ret := _m.Called(policyID, offset, limit) + + var r0 *model.ChannelsWithCount + if rf, ok := ret.Get(0).(func(string, int, int) *model.ChannelsWithCount); ok { + r0 = rf(policyID, offset, limit) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.ChannelsWithCount) + } + } + + var r1 *model.AppError + if rf, ok := ret.Get(1).(func(string, int, int) *model.AppError); ok { + r1 = rf(policyID, offset, limit) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*model.AppError) + } + } + + return r0, r1 +} + +// GetGlobalPolicy provides a mock function with given fields: +func (_m *DataRetentionInterface) GetGlobalPolicy() (*model.GlobalRetentionPolicy, *model.AppError) { ret := _m.Called() - var r0 *model.DataRetentionPolicy - if rf, ok := ret.Get(0).(func() *model.DataRetentionPolicy); ok { + var r0 *model.GlobalRetentionPolicy + if rf, ok := ret.Get(0).(func() *model.GlobalRetentionPolicy); ok { r0 = rf() } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(*model.DataRetentionPolicy) + r0 = ret.Get(0).(*model.GlobalRetentionPolicy) } } @@ -38,3 +161,183 @@ func (_m *DataRetentionInterface) GetPolicy() (*model.DataRetentionPolicy, *mode return r0, r1 } + +// GetPolicies provides a mock function with given fields: offset, limit +func (_m *DataRetentionInterface) GetPolicies(offset int, limit int) (*model.RetentionPolicyWithTeamAndChannelCountsList, *model.AppError) { + ret := _m.Called(offset, limit) + + var r0 *model.RetentionPolicyWithTeamAndChannelCountsList + if rf, ok := ret.Get(0).(func(int, int) *model.RetentionPolicyWithTeamAndChannelCountsList); ok { + r0 = rf(offset, limit) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.RetentionPolicyWithTeamAndChannelCountsList) + } + } + + var r1 *model.AppError + if rf, ok := ret.Get(1).(func(int, int) *model.AppError); ok { + r1 = rf(offset, limit) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*model.AppError) + } + } + + return r0, r1 +} + +// GetPoliciesCount provides a mock function with given fields: +func (_m *DataRetentionInterface) GetPoliciesCount() (int64, *model.AppError) { + ret := _m.Called() + + var r0 int64 + if rf, ok := ret.Get(0).(func() int64); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(int64) + } + + var r1 *model.AppError + if rf, ok := ret.Get(1).(func() *model.AppError); ok { + r1 = rf() + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*model.AppError) + } + } + + return r0, r1 +} + +// GetPolicy provides a mock function with given fields: policyID +func (_m *DataRetentionInterface) GetPolicy(policyID string) (*model.RetentionPolicyWithTeamAndChannelCounts, *model.AppError) { + ret := _m.Called(policyID) + + var r0 *model.RetentionPolicyWithTeamAndChannelCounts + if rf, ok := ret.Get(0).(func(string) *model.RetentionPolicyWithTeamAndChannelCounts); ok { + r0 = rf(policyID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.RetentionPolicyWithTeamAndChannelCounts) + } + } + + var r1 *model.AppError + if rf, ok := ret.Get(1).(func(string) *model.AppError); ok { + r1 = rf(policyID) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*model.AppError) + } + } + + return r0, r1 +} + +// GetTeamPoliciesForUser provides a mock function with given fields: userID, offset, limit +func (_m *DataRetentionInterface) GetTeamPoliciesForUser(userID string, offset int, limit int) (*model.RetentionPolicyForTeamList, *model.AppError) { + ret := _m.Called(userID, offset, limit) + + var r0 *model.RetentionPolicyForTeamList + if rf, ok := ret.Get(0).(func(string, int, int) *model.RetentionPolicyForTeamList); ok { + r0 = rf(userID, offset, limit) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.RetentionPolicyForTeamList) + } + } + + var r1 *model.AppError + if rf, ok := ret.Get(1).(func(string, int, int) *model.AppError); ok { + r1 = rf(userID, offset, limit) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*model.AppError) + } + } + + return r0, r1 +} + +// GetTeamsForPolicy provides a mock function with given fields: policyID, offset, limit +func (_m *DataRetentionInterface) GetTeamsForPolicy(policyID string, offset int, limit int) (*model.TeamsWithCount, *model.AppError) { + ret := _m.Called(policyID, offset, limit) + + var r0 *model.TeamsWithCount + if rf, ok := ret.Get(0).(func(string, int, int) *model.TeamsWithCount); ok { + r0 = rf(policyID, offset, limit) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.TeamsWithCount) + } + } + + var r1 *model.AppError + if rf, ok := ret.Get(1).(func(string, int, int) *model.AppError); ok { + r1 = rf(policyID, offset, limit) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*model.AppError) + } + } + + return r0, r1 +} + +// PatchPolicy provides a mock function with given fields: patch +func (_m *DataRetentionInterface) PatchPolicy(patch *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, *model.AppError) { + ret := _m.Called(patch) + + var r0 *model.RetentionPolicyWithTeamAndChannelCounts + if rf, ok := ret.Get(0).(func(*model.RetentionPolicyWithTeamAndChannelIDs) *model.RetentionPolicyWithTeamAndChannelCounts); ok { + r0 = rf(patch) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.RetentionPolicyWithTeamAndChannelCounts) + } + } + + var r1 *model.AppError + if rf, ok := ret.Get(1).(func(*model.RetentionPolicyWithTeamAndChannelIDs) *model.AppError); ok { + r1 = rf(patch) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*model.AppError) + } + } + + return r0, r1 +} + +// RemoveChannelsFromPolicy provides a mock function with given fields: policyID, channelIDs +func (_m *DataRetentionInterface) RemoveChannelsFromPolicy(policyID string, channelIDs []string) *model.AppError { + ret := _m.Called(policyID, channelIDs) + + var r0 *model.AppError + if rf, ok := ret.Get(0).(func(string, []string) *model.AppError); ok { + r0 = rf(policyID, channelIDs) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.AppError) + } + } + + return r0 +} + +// RemoveTeamsFromPolicy provides a mock function with given fields: policyID, teamIDs +func (_m *DataRetentionInterface) RemoveTeamsFromPolicy(policyID string, teamIDs []string) *model.AppError { + ret := _m.Called(policyID, teamIDs) + + var r0 *model.AppError + if rf, ok := ret.Get(0).(func(string, []string) *model.AppError); ok { + r0 = rf(policyID, teamIDs) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.AppError) + } + } + + return r0 +} diff --git a/i18n/en.json b/i18n/en.json index 20308c1605..07532a7d03 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -6066,14 +6066,6 @@ "id": "app.system_install_date.parse_int.app_error", "translation": "Failed to parse installation date." }, - { - "id": "app.team.analytics_private_team_count.app_error", - "translation": "Unable to count the private teams." - }, - { - "id": "app.team.analytics_public_team_count.app_error", - "translation": "Unable to count the public teams." - }, { "id": "app.team.analytics_team_count.app_error", "translation": "Unable to count the teams." @@ -6102,10 +6094,6 @@ "id": "app.team.get_all_private_team_listing.app_error", "translation": "We could not get all private teams." }, - { - "id": "app.team.get_all_private_team_page_listing.app_error", - "translation": "We could not get all private teams in page." - }, { "id": "app.team.get_all_team_listing.app_error", "translation": "We could not get all teams." @@ -6946,6 +6934,14 @@ "id": "ent.data_retention.generic.license.error", "translation": "Your license does not support Data Retention." }, + { + "id": "ent.data_retention.policies.internal_error", + "translation": "We encountered an error performing the requested operation." + }, + { + "id": "ent.data_retention.policies.invalid_policy", + "translation": "Policy is invalid." + }, { "id": "ent.data_retention.posts_permanent_delete_batch.internal_error", "translation": "We encountered an error permanently deleting the batch of posts." diff --git a/model/channel.go b/model/channel.go index 15c080a145..8dc3fa8d90 100644 --- a/model/channel.go +++ b/model/channel.go @@ -53,6 +53,7 @@ type Channel struct { GroupConstrained *bool `json:"group_constrained"` Shared *bool `json:"shared"` TotalMsgCountRoot int64 `json:"total_msg_count_root"` + PolicyID *string `json:"policy_id" db:"-"` } type ChannelWithTeamData struct { @@ -122,18 +123,21 @@ type ChannelModeratedRolesPatch struct { // PerPage number of results per page, if paginated. // type ChannelSearchOpts struct { - NotAssociatedToGroup string - ExcludeDefaultChannels bool - IncludeDeleted bool - Deleted bool - ExcludeChannelNames []string - TeamIds []string - GroupConstrained bool - ExcludeGroupConstrained bool - Public bool - Private bool - Page *int - PerPage *int + NotAssociatedToGroup string + ExcludeDefaultChannels bool + IncludeDeleted bool + Deleted bool + ExcludeChannelNames []string + TeamIds []string + GroupConstrained bool + ExcludeGroupConstrained bool + PolicyID string + ExcludePolicyConstrained bool + IncludePolicyID bool + Public bool + Private bool + Page *int + PerPage *int } type ChannelMemberCountByGroup struct { diff --git a/model/channel_search.go b/model/channel_search.go index 87fd3aef6d..51e117363b 100644 --- a/model/channel_search.go +++ b/model/channel_search.go @@ -11,18 +11,19 @@ import ( const CHANNEL_SEARCH_DEFAULT_LIMIT = 50 type ChannelSearch struct { - Term string `json:"term"` - ExcludeDefaultChannels bool `json:"exclude_default_channels"` - NotAssociatedToGroup string `json:"not_associated_to_group"` - TeamIds []string `json:"team_ids"` - GroupConstrained bool `json:"group_constrained"` - ExcludeGroupConstrained bool `json:"exclude_group_constrained"` - Public bool `json:"public"` - Private bool `json:"private"` - IncludeDeleted bool `json:"include_deleted"` - Deleted bool `json:"deleted"` - Page *int `json:"page,omitempty"` - PerPage *int `json:"per_page,omitempty"` + Term string `json:"term"` + ExcludeDefaultChannels bool `json:"exclude_default_channels"` + NotAssociatedToGroup string `json:"not_associated_to_group"` + TeamIds []string `json:"team_ids"` + GroupConstrained bool `json:"group_constrained"` + ExcludeGroupConstrained bool `json:"exclude_group_constrained"` + ExcludePolicyConstrained bool `json:"exclude_policy_constrained"` + Public bool `json:"public"` + Private bool `json:"private"` + IncludeDeleted bool `json:"include_deleted"` + Deleted bool `json:"deleted"` + Page *int `json:"page,omitempty"` + PerPage *int `json:"per_page,omitempty"` } // ToJson convert a Channel to a json string diff --git a/model/client4.go b/model/client4.go index 0d2f5d4f97..6898f7720a 100644 --- a/model/client4.go +++ b/model/client4.go @@ -429,6 +429,10 @@ func (c *Client4) GetDataRetentionRoute() string { return "/data_retention" } +func (c *Client4) GetDataRetentionPolicyRoute(policyID string) string { + return fmt.Sprintf(c.GetDataRetentionRoute()+"/policies/%v", policyID) +} + func (c *Client4) GetElasticsearchRoute() string { return "/elasticsearch" } @@ -577,6 +581,14 @@ func (c *Client4) DoApiPost(url string, data string) (*http.Response, *AppError) return c.DoApiRequest(http.MethodPost, c.ApiUrl+url, data, "") } +func (c *Client4) doApiDeleteBytes(url string, data []byte) (*http.Response, *AppError) { + return c.doApiRequestBytes(http.MethodDelete, c.ApiUrl+url, data, "") +} + +func (c *Client4) doApiPatchBytes(url string, data []byte) (*http.Response, *AppError) { + return c.doApiRequestBytes(http.MethodPatch, c.ApiUrl+url, data, "") +} + func (c *Client4) doApiPostBytes(url string, data []byte) (*http.Response, *AppError) { return c.doApiRequestBytes(http.MethodPost, c.ApiUrl+url, data, "") } @@ -1853,6 +1865,18 @@ func (c *Client4) GetAllTeamsWithTotalCount(etag string, page int, perPage int) return teamsListWithCount.Teams, teamsListWithCount.TotalCount, BuildResponse(r) } +// GetAllTeamsExcludePolicyConstrained returns all teams which are not part of a data retention policy. +// Must be a system administrator. +func (c *Client4) GetAllTeamsExcludePolicyConstrained(etag string, page int, perPage int) ([]*Team, *Response) { + query := fmt.Sprintf("?page=%v&per_page=%v&exclude_policy_constrained=%v", page, perPage, true) + r, err := c.DoApiGet(c.GetTeamsRoute()+query, etag) + if err != nil { + return nil, BuildErrorResponse(r, err) + } + defer closeBody(r) + return TeamListFromJson(r.Body), BuildResponse(r) +} + // GetTeamByName returns a team based on the provided team name string. func (c *Client4) GetTeamByName(name, etag string) (*Team, *Response) { r, err := c.DoApiGet(c.GetTeamByNameRoute(name), etag) @@ -2347,16 +2371,23 @@ func (c *Client4) RemoveTeamIcon(teamId string) (bool, *Response) { // GetAllChannels get all the channels. Must be a system administrator. func (c *Client4) GetAllChannels(page int, perPage int, etag string) (*ChannelListWithTeamData, *Response) { - return c.getAllChannels(page, perPage, etag, false) + return c.getAllChannels(page, perPage, etag, ChannelSearchOpts{}) } // GetAllChannelsIncludeDeleted get all the channels. Must be a system administrator. func (c *Client4) GetAllChannelsIncludeDeleted(page int, perPage int, etag string) (*ChannelListWithTeamData, *Response) { - return c.getAllChannels(page, perPage, etag, true) + return c.getAllChannels(page, perPage, etag, ChannelSearchOpts{IncludeDeleted: true}) } -func (c *Client4) getAllChannels(page int, perPage int, etag string, includeDeleted bool) (*ChannelListWithTeamData, *Response) { - query := fmt.Sprintf("?page=%v&per_page=%v&include_deleted=%v", page, perPage, includeDeleted) +// GetAllChannelsExcludePolicyConstrained gets all channels which are not part of a data retention policy. +// Must be a system administrator. +func (c *Client4) GetAllChannelsExcludePolicyConstrained(page, perPage int, etag string) (*ChannelListWithTeamData, *Response) { + return c.getAllChannels(page, perPage, etag, ChannelSearchOpts{ExcludePolicyConstrained: true}) +} + +func (c *Client4) getAllChannels(page int, perPage int, etag string, opts ChannelSearchOpts) (*ChannelListWithTeamData, *Response) { + query := fmt.Sprintf("?page=%v&per_page=%v&include_deleted=%v&exclude_policy_constrained=%v", + page, perPage, opts.IncludeDeleted, opts.ExcludePolicyConstrained) r, err := c.DoApiGet(c.GetChannelsRoute()+query, etag) if err != nil { return nil, BuildErrorResponse(r, err) @@ -4540,14 +4571,236 @@ func (c *Client4) PurgeBleveIndexes() (bool, *Response) { // Data Retention Section -// GetDataRetentionPolicy will get the current server data retention policy details. -func (c *Client4) GetDataRetentionPolicy() (*DataRetentionPolicy, *Response) { +// GetDataRetentionPolicy will get the current global data retention policy details. +func (c *Client4) GetDataRetentionPolicy() (*GlobalRetentionPolicy, *Response) { r, err := c.DoApiGet(c.GetDataRetentionRoute()+"/policy", "") if err != nil { return nil, BuildErrorResponse(r, err) } defer closeBody(r) - return DataRetentionPolicyFromJson(r.Body), BuildResponse(r) + return GlobalRetentionPolicyFromJson(r.Body), BuildResponse(r) +} + +// GetDataRetentionPolicyByID will get the details for the granular data retention policy with the specified ID. +func (c *Client4) GetDataRetentionPolicyByID(policyID string) (*RetentionPolicyWithTeamAndChannelCounts, *Response) { + r, appErr := c.DoApiGet(c.GetDataRetentionPolicyRoute(policyID), "") + if appErr != nil { + return nil, BuildErrorResponse(r, appErr) + } + defer closeBody(r) + policy, err := RetentionPolicyWithTeamAndChannelCountsFromJson(r.Body) + if err != nil { + return nil, BuildErrorResponse(r, NewAppError("Client4.GetDataRetentionPolicyByID", "model.utils.decode_json.app_error", nil, err.Error(), r.StatusCode)) + } + return policy, BuildResponse(r) +} + +// GetDataRetentionPoliciesCount will get the total number of granular data retention policies. +func (c *Client4) GetDataRetentionPoliciesCount() (int64, *Response) { + type CountBody struct { + TotalCount int64 `json:"total_count"` + } + r, appErr := c.DoApiGet(c.GetDataRetentionRoute()+"/policies_count", "") + if appErr != nil { + return 0, BuildErrorResponse(r, appErr) + } + var countObj CountBody + jsonErr := json.NewDecoder(r.Body).Decode(&countObj) + if jsonErr != nil { + return 0, BuildErrorResponse(r, NewAppError("Client4.GetDataRetentionPoliciesCount", "model.utils.decode_json.app_error", nil, jsonErr.Error(), r.StatusCode)) + } + return countObj.TotalCount, BuildResponse(r) +} + +// GetDataRetentionPolicies will get the current granular data retention policies' details. +func (c *Client4) GetDataRetentionPolicies(page, perPage int) (*RetentionPolicyWithTeamAndChannelCountsList, *Response) { + query := fmt.Sprintf("?page=%d&per_page=%d", page, perPage) + r, appErr := c.DoApiGet(c.GetDataRetentionRoute()+"/policies"+query, "") + if appErr != nil { + return nil, BuildErrorResponse(r, appErr) + } + defer closeBody(r) + policies, err := RetentionPolicyWithTeamAndChannelCountsListFromJson(r.Body) + if err != nil { + return nil, BuildErrorResponse(r, NewAppError("Client4.GetDataRetentionPolicies", "model.utils.decode_json.app_error", nil, err.Error(), r.StatusCode)) + } + return policies, BuildResponse(r) +} + +// CreateDataRetentionPolicy will create a new granular data retention policy which will be applied to +// the specified teams and channels. The Id field of `policy` must be empty. +func (c *Client4) CreateDataRetentionPolicy(policy *RetentionPolicyWithTeamAndChannelIDs) (*RetentionPolicyWithTeamAndChannelCounts, *Response) { + r, appErr := c.doApiPostBytes(c.GetDataRetentionRoute()+"/policies", policy.ToJson()) + if appErr != nil { + return nil, BuildErrorResponse(r, appErr) + } + defer closeBody(r) + newPolicy, err := RetentionPolicyWithTeamAndChannelCountsFromJson(r.Body) + if err != nil { + return nil, BuildErrorResponse(r, NewAppError("Client4.CreateDataRetentionPolicy", "model.utils.decode_json.app_error", nil, err.Error(), r.StatusCode)) + } + return newPolicy, BuildResponse(r) +} + +// DeleteDataRetentionPolicy will delete the granular data retention policy with the specified ID. +func (c *Client4) DeleteDataRetentionPolicy(policyID string) *Response { + r, appErr := c.DoApiDelete(c.GetDataRetentionPolicyRoute(policyID)) + if appErr != nil { + return BuildErrorResponse(r, appErr) + } + defer closeBody(r) + return BuildResponse(r) +} + +// PatchDataRetentionPolicy will patch the granular data retention policy with the specified ID. +// The Id field of `patch` must be non-empty. +func (c *Client4) PatchDataRetentionPolicy(patch *RetentionPolicyWithTeamAndChannelIDs) (*RetentionPolicyWithTeamAndChannelCounts, *Response) { + r, appErr := c.doApiPatchBytes(c.GetDataRetentionPolicyRoute(patch.ID), patch.ToJson()) + if appErr != nil { + return nil, BuildErrorResponse(r, appErr) + } + defer closeBody(r) + policy, err := RetentionPolicyWithTeamAndChannelCountsFromJson(r.Body) + if err != nil { + return nil, BuildErrorResponse(r, NewAppError("Client4.PatchDataRetentionPolicy", "model.utils.decode_json.app_error", nil, err.Error(), r.StatusCode)) + } + return policy, BuildResponse(r) +} + +// GetTeamsForRetentionPolicy will get the teams to which the specified policy is currently applied. +func (c *Client4) GetTeamsForRetentionPolicy(policyID string, page, perPage int) (*TeamsWithCount, *Response) { + query := fmt.Sprintf("?page=%d&per_page=%d", page, perPage) + r, appErr := c.DoApiGet(c.GetDataRetentionPolicyRoute(policyID)+"/teams"+query, "") + if appErr != nil { + return nil, BuildErrorResponse(r, appErr) + } + var teams *TeamsWithCount + jsonErr := json.NewDecoder(r.Body).Decode(&teams) + if jsonErr != nil { + return nil, BuildErrorResponse(r, NewAppError("Client4.GetTeamsForRetentionPolicy", "model.utils.decode_json.app_error", nil, jsonErr.Error(), r.StatusCode)) + } + return teams, BuildResponse(r) +} + +// SearchTeamsForRetentionPolicy will search the teams to which the specified policy is currently applied. +func (c *Client4) SearchTeamsForRetentionPolicy(policyID string, term string) ([]*Team, *Response) { + body, _ := json.Marshal(map[string]interface{}{"term": term}) + r, appErr := c.doApiPostBytes(c.GetDataRetentionPolicyRoute(policyID)+"/teams/search", body) + if appErr != nil { + return nil, BuildErrorResponse(r, appErr) + } + var teams []*Team + jsonErr := json.NewDecoder(r.Body).Decode(&teams) + if jsonErr != nil { + return nil, BuildErrorResponse(r, NewAppError("Client4.SearchTeamsForRetentionPolicy", "model.utils.decode_json.app_error", nil, jsonErr.Error(), r.StatusCode)) + } + return teams, BuildResponse(r) +} + +// AddTeamsToRetentionPolicy will add the specified teams to the granular data retention policy +// with the specified ID. +func (c *Client4) AddTeamsToRetentionPolicy(policyID string, teamIDs []string) *Response { + body, _ := json.Marshal(teamIDs) + r, appErr := c.doApiPostBytes(c.GetDataRetentionPolicyRoute(policyID)+"/teams", body) + if appErr != nil { + return BuildErrorResponse(r, appErr) + } + defer closeBody(r) + return BuildResponse(r) +} + +// RemoveTeamsFromRetentionPolicy will remove the specified teams from the granular data retention policy +// with the specified ID. +func (c *Client4) RemoveTeamsFromRetentionPolicy(policyID string, teamIDs []string) *Response { + body, _ := json.Marshal(teamIDs) + r, appErr := c.doApiDeleteBytes(c.GetDataRetentionPolicyRoute(policyID)+"/teams", body) + if appErr != nil { + return BuildErrorResponse(r, appErr) + } + defer closeBody(r) + return BuildResponse(r) +} + +// GetChannelsForRetentionPolicy will get the channels to which the specified policy is currently applied. +func (c *Client4) GetChannelsForRetentionPolicy(policyID string, page, perPage int) (*ChannelsWithCount, *Response) { + query := fmt.Sprintf("?page=%d&per_page=%d", page, perPage) + r, appErr := c.DoApiGet(c.GetDataRetentionPolicyRoute(policyID)+"/channels"+query, "") + if appErr != nil { + return nil, BuildErrorResponse(r, appErr) + } + var channels *ChannelsWithCount + jsonErr := json.NewDecoder(r.Body).Decode(&channels) + if jsonErr != nil { + return nil, BuildErrorResponse(r, NewAppError("Client4.GetChannelsForRetentionPolicy", "model.utils.decode_json.app_error", nil, jsonErr.Error(), r.StatusCode)) + } + return channels, BuildResponse(r) +} + +// SearchChannelsForRetentionPolicy will search the channels to which the specified policy is currently applied. +func (c *Client4) SearchChannelsForRetentionPolicy(policyID string, term string) (ChannelListWithTeamData, *Response) { + body, _ := json.Marshal(map[string]interface{}{"term": term}) + r, appErr := c.doApiPostBytes(c.GetDataRetentionPolicyRoute(policyID)+"/channels/search", body) + if appErr != nil { + return nil, BuildErrorResponse(r, appErr) + } + var channels ChannelListWithTeamData + jsonErr := json.NewDecoder(r.Body).Decode(&channels) + if jsonErr != nil { + return nil, BuildErrorResponse(r, NewAppError("Client4.SearchChannelsForRetentionPolicy", "model.utils.decode_json.app_error", nil, jsonErr.Error(), r.StatusCode)) + } + return channels, BuildResponse(r) +} + +// AddChannelsToRetentionPolicy will add the specified channels to the granular data retention policy +// with the specified ID. +func (c *Client4) AddChannelsToRetentionPolicy(policyID string, channelIDs []string) *Response { + body, _ := json.Marshal(channelIDs) + r, appErr := c.doApiPostBytes(c.GetDataRetentionPolicyRoute(policyID)+"/channels", body) + if appErr != nil { + return BuildErrorResponse(r, appErr) + } + defer closeBody(r) + return BuildResponse(r) +} + +// RemoveChannelsFromRetentionPolicy will remove the specified channels from the granular data retention policy +// with the specified ID. +func (c *Client4) RemoveChannelsFromRetentionPolicy(policyID string, channelIDs []string) *Response { + body, _ := json.Marshal(channelIDs) + r, appErr := c.doApiDeleteBytes(c.GetDataRetentionPolicyRoute(policyID)+"/channels", body) + if appErr != nil { + return BuildErrorResponse(r, appErr) + } + defer closeBody(r) + return BuildResponse(r) +} + +// GetTeamPoliciesForUser will get the data retention policies for the teams to which a user belongs. +func (c *Client4) GetTeamPoliciesForUser(userID string, offset, limit int) (*RetentionPolicyForTeamList, *Response) { + r, appErr := c.DoApiGet(c.GetUserRoute(userID)+"/data_retention/team_policies", "") + if appErr != nil { + return nil, BuildErrorResponse(r, appErr) + } + var teams RetentionPolicyForTeamList + jsonErr := json.NewDecoder(r.Body).Decode(&teams) + if jsonErr != nil { + return nil, BuildErrorResponse(r, NewAppError("Client4.GetTeamPoliciesForUser", "model.utils.decode_json.app_error", nil, jsonErr.Error(), r.StatusCode)) + } + return &teams, BuildResponse(r) +} + +// GetChannelPoliciesForUser will get the data retention policies for the channels to which a user belongs. +func (c *Client4) GetChannelPoliciesForUser(userID string, offset, limit int) (*RetentionPolicyForChannelList, *Response) { + r, appErr := c.DoApiGet(c.GetUserRoute(userID)+"/data_retention/channel_policies", "") + if appErr != nil { + return nil, BuildErrorResponse(r, appErr) + } + var channels RetentionPolicyForChannelList + jsonErr := json.NewDecoder(r.Body).Decode(&channels) + if jsonErr != nil { + return nil, BuildErrorResponse(r, NewAppError("Client4.GetChannelPoliciesForUser", "model.utils.decode_json.app_error", nil, jsonErr.Error(), r.StatusCode)) + } + return &channels, BuildResponse(r) } // Commands Section diff --git a/model/data_retention_policy.go b/model/data_retention_policy.go index 42fb969d09..2c0d6099a4 100644 --- a/model/data_retention_policy.go +++ b/model/data_retention_policy.go @@ -8,20 +8,119 @@ import ( "io" ) -type DataRetentionPolicy struct { +type GlobalRetentionPolicy struct { MessageDeletionEnabled bool `json:"message_deletion_enabled"` FileDeletionEnabled bool `json:"file_deletion_enabled"` MessageRetentionCutoff int64 `json:"message_retention_cutoff"` FileRetentionCutoff int64 `json:"file_retention_cutoff"` } -func (drp *DataRetentionPolicy) ToJson() string { - b, _ := json.Marshal(drp) - return string(b) +type RetentionPolicy struct { + ID string `db:"Id" json:"id"` + DisplayName string `json:"display_name"` + PostDuration *int64 `json:"post_duration"` } -func DataRetentionPolicyFromJson(data io.Reader) *DataRetentionPolicy { - var drp *DataRetentionPolicy - json.NewDecoder(data).Decode(&drp) - return drp +type RetentionPolicyWithTeamAndChannelIDs struct { + RetentionPolicy + TeamIDs []string `json:"team_ids"` + ChannelIDs []string `json:"channel_ids"` +} + +type RetentionPolicyWithTeamAndChannelCounts struct { + RetentionPolicy + ChannelCount int64 `json:"channel_count"` + TeamCount int64 `json:"team_count"` +} + +type RetentionPolicyChannel struct { + PolicyID string `db:"PolicyId"` + ChannelID string `db:"ChannelId"` +} + +type RetentionPolicyTeam struct { + PolicyID string `db:"PolicyId"` + TeamID string `db:"TeamId"` +} + +type RetentionPolicyWithTeamAndChannelCountsList struct { + Policies []*RetentionPolicyWithTeamAndChannelCounts `json:"policies"` + TotalCount int64 `json:"total_count"` +} + +type RetentionPolicyForTeam struct { + TeamID string `db:"Id" json:"team_id"` + PostDuration int64 `json:"post_duration"` +} + +type RetentionPolicyForTeamList struct { + Policies []*RetentionPolicyForTeam `json:"policies"` + TotalCount int64 `json:"total_count"` +} + +type RetentionPolicyForChannel struct { + ChannelID string `db:"Id" json:"channel_id"` + PostDuration int64 `json:"post_duration"` +} + +type RetentionPolicyForChannelList struct { + Policies []*RetentionPolicyForChannel `json:"policies"` + TotalCount int64 `json:"total_count"` +} + +func (rp *GlobalRetentionPolicy) ToJson() []byte { + b, _ := json.Marshal(rp) + return b +} + +func GlobalRetentionPolicyFromJson(data io.Reader) *GlobalRetentionPolicy { + var grp *GlobalRetentionPolicy + json.NewDecoder(data).Decode(&grp) + return grp +} + +func RetentionPolicyWithTeamAndChannelCountsFromJson(data io.Reader) (*RetentionPolicyWithTeamAndChannelCounts, error) { + var rp RetentionPolicyWithTeamAndChannelCounts + err := json.NewDecoder(data).Decode(&rp) + return &rp, err +} + +func (rp *RetentionPolicyWithTeamAndChannelCounts) ToJson() []byte { + b, _ := json.Marshal(rp) + return b +} + +func RetentionPolicyWithTeamAndChannelCountsListFromJson(data io.Reader) (*RetentionPolicyWithTeamAndChannelCountsList, error) { + var rpList *RetentionPolicyWithTeamAndChannelCountsList + err := json.NewDecoder(data).Decode(&rpList) + if err != nil { + return nil, err + } + return rpList, nil +} + +func (rpList *RetentionPolicyWithTeamAndChannelCountsList) ToJson() []byte { + b, _ := json.Marshal(rpList) + return b +} + +func RetentionPolicyWithTeamAndChannelIdsFromJson(data io.Reader) (*RetentionPolicyWithTeamAndChannelIDs, error) { + var rp *RetentionPolicyWithTeamAndChannelIDs + err := json.NewDecoder(data).Decode(&rp) + return rp, err +} + +func (rp *RetentionPolicyWithTeamAndChannelIDs) ToJson() []byte { + b, _ := json.Marshal(rp) + return b +} + +func (lst *RetentionPolicyForTeamList) ToJson() []byte { + b, _ := json.Marshal(lst) + return b +} + +func (lst *RetentionPolicyForChannelList) ToJson() []byte { + b, _ := json.Marshal(lst) + return b } diff --git a/model/team.go b/model/team.go index bb254fa21a..fc752f30ed 100644 --- a/model/team.go +++ b/model/team.go @@ -42,6 +42,7 @@ type Team struct { LastTeamIconUpdate int64 `json:"last_team_icon_update,omitempty"` SchemeId *string `json:"scheme_id"` GroupConstrained *bool `json:"group_constrained"` + PolicyID *string `json:"policy_id" db:"-"` } type TeamPatch struct { diff --git a/model/team_search.go b/model/team_search.go index f9de580186..e24b84387c 100644 --- a/model/team_search.go +++ b/model/team_search.go @@ -9,12 +9,17 @@ import ( ) type TeamSearch struct { - Term string `json:"term"` - Page *int `json:"page,omitempty"` - PerPage *int `json:"per_page,omitempty"` - AllowOpenInvite *bool `json:"allow_open_invite,omitempty"` - GroupConstrained *bool `json:"group_constrained,omitempty"` - IncludeGroupConstrained *bool `json:"include_group_constrained,omitempty"` + Term string `json:"term"` + Page *int `json:"page,omitempty"` + PerPage *int `json:"per_page,omitempty"` + AllowOpenInvite *bool `json:"allow_open_invite,omitempty"` + GroupConstrained *bool `json:"group_constrained,omitempty"` + IncludeGroupConstrained *bool `json:"include_group_constrained,omitempty"` + PolicyID *string `json:"policy_id,omitempty"` + ExcludePolicyConstrained *bool `json:"exclude_policy_constrained,omitempty"` + IncludePolicyID *bool `json:"-"` + IncludeDeleted *bool `json:"-"` + TeamType *string `json:"-"` } func (t *TeamSearch) IsPaginated() bool { diff --git a/model/utils.go b/model/utils.go index 775a82c3cf..903bc0d0d3 100644 --- a/model/utils.go +++ b/model/utils.go @@ -330,6 +330,12 @@ func StringFromJson(data io.Reader) string { return s } +// ToJson serializes an arbitrary data type to JSON, discarding the error. +func ToJson(v interface{}) []byte { + b, _ := json.Marshal(v) + return b +} + func GetServerIpAddress(iface string) string { var addrs []net.Addr if iface == "" { diff --git a/services/telemetry/telemetry.go b/services/telemetry/telemetry.go index 56a8466adb..6c03ba48b7 100644 --- a/services/telemetry/telemetry.go +++ b/services/telemetry/telemetry.go @@ -274,7 +274,7 @@ func (ts *TelemetryService) trackActivity() { inactiveUserCount = iucr } - teamCount, err := ts.dbStore.Team().AnalyticsTeamCount(false) + teamCount, err := ts.dbStore.Team().AnalyticsTeamCount(nil) if err != nil { mlog.Info("Could not get team count", mlog.Err(err)) } diff --git a/services/telemetry/telemetry_test.go b/services/telemetry/telemetry_test.go index b89c6a9ae7..5465989b4c 100644 --- a/services/telemetry/telemetry_test.go +++ b/services/telemetry/telemetry_test.go @@ -91,7 +91,7 @@ func initializeMocks(cfg *model.Config) (*mocks.ServerIface, *storeMocks.Store, userStore.On("AnalyticsGetSystemAdminCount").Return(int64(9), nil) teamStore := storeMocks.TeamStore{} - teamStore.On("AnalyticsTeamCount", false).Return(int64(3), nil) + teamStore.On("AnalyticsTeamCount", (*model.TeamSearch)(nil)).Return(int64(3), nil) teamStore.On("GroupSyncedTeamCount").Return(int64(16), nil) channelStore := storeMocks.ChannelStore{} diff --git a/store/opentracinglayer/opentracinglayer.go b/store/opentracinglayer/opentracinglayer.go index 5460df76ca..09da5faaad 100644 --- a/store/opentracinglayer/opentracinglayer.go +++ b/store/opentracinglayer/opentracinglayer.go @@ -39,6 +39,7 @@ type OpenTracingLayer struct { ProductNoticesStore store.ProductNoticesStore ReactionStore store.ReactionStore RemoteClusterStore store.RemoteClusterStore + RetentionPolicyStore store.RetentionPolicyStore RoleStore store.RoleStore SchemeStore store.SchemeStore SessionStore store.SessionStore @@ -140,6 +141,10 @@ func (s *OpenTracingLayer) RemoteCluster() store.RemoteClusterStore { return s.RemoteClusterStore } +func (s *OpenTracingLayer) RetentionPolicy() store.RetentionPolicyStore { + return s.RetentionPolicyStore +} + func (s *OpenTracingLayer) Role() store.RoleStore { return s.RoleStore } @@ -305,6 +310,11 @@ type OpenTracingLayerRemoteClusterStore struct { Root *OpenTracingLayer } +type OpenTracingLayerRetentionPolicyStore struct { + store.RetentionPolicyStore + Root *OpenTracingLayer +} + type OpenTracingLayerRoleStore struct { store.RoleStore Root *OpenTracingLayer @@ -6099,6 +6109,330 @@ func (s *OpenTracingLayerRemoteClusterStore) UpdateTopics(remoteClusterId string return result, err } +func (s *OpenTracingLayerRetentionPolicyStore) AddChannels(policyId string, channelIds []string) error { + origCtx := s.Root.Store.Context() + span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "RetentionPolicyStore.AddChannels") + s.Root.Store.SetContext(newCtx) + defer func() { + s.Root.Store.SetContext(origCtx) + }() + + defer span.Finish() + err := s.RetentionPolicyStore.AddChannels(policyId, channelIds) + if err != nil { + span.LogFields(spanlog.Error(err)) + ext.Error.Set(span, true) + } + + return err +} + +func (s *OpenTracingLayerRetentionPolicyStore) AddTeams(policyId string, teamIds []string) error { + origCtx := s.Root.Store.Context() + span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "RetentionPolicyStore.AddTeams") + s.Root.Store.SetContext(newCtx) + defer func() { + s.Root.Store.SetContext(origCtx) + }() + + defer span.Finish() + err := s.RetentionPolicyStore.AddTeams(policyId, teamIds) + if err != nil { + span.LogFields(spanlog.Error(err)) + ext.Error.Set(span, true) + } + + return err +} + +func (s *OpenTracingLayerRetentionPolicyStore) Delete(id string) error { + origCtx := s.Root.Store.Context() + span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "RetentionPolicyStore.Delete") + s.Root.Store.SetContext(newCtx) + defer func() { + s.Root.Store.SetContext(origCtx) + }() + + defer span.Finish() + err := s.RetentionPolicyStore.Delete(id) + if err != nil { + span.LogFields(spanlog.Error(err)) + ext.Error.Set(span, true) + } + + return err +} + +func (s *OpenTracingLayerRetentionPolicyStore) Get(id string) (*model.RetentionPolicyWithTeamAndChannelCounts, error) { + origCtx := s.Root.Store.Context() + span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "RetentionPolicyStore.Get") + s.Root.Store.SetContext(newCtx) + defer func() { + s.Root.Store.SetContext(origCtx) + }() + + defer span.Finish() + result, err := s.RetentionPolicyStore.Get(id) + if err != nil { + span.LogFields(spanlog.Error(err)) + ext.Error.Set(span, true) + } + + return result, err +} + +func (s *OpenTracingLayerRetentionPolicyStore) GetAll(offset int, limit int) ([]*model.RetentionPolicyWithTeamAndChannelCounts, error) { + origCtx := s.Root.Store.Context() + span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "RetentionPolicyStore.GetAll") + s.Root.Store.SetContext(newCtx) + defer func() { + s.Root.Store.SetContext(origCtx) + }() + + defer span.Finish() + result, err := s.RetentionPolicyStore.GetAll(offset, limit) + if err != nil { + span.LogFields(spanlog.Error(err)) + ext.Error.Set(span, true) + } + + return result, err +} + +func (s *OpenTracingLayerRetentionPolicyStore) GetChannelPoliciesCountForUser(userID string) (int64, error) { + origCtx := s.Root.Store.Context() + span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "RetentionPolicyStore.GetChannelPoliciesCountForUser") + s.Root.Store.SetContext(newCtx) + defer func() { + s.Root.Store.SetContext(origCtx) + }() + + defer span.Finish() + result, err := s.RetentionPolicyStore.GetChannelPoliciesCountForUser(userID) + if err != nil { + span.LogFields(spanlog.Error(err)) + ext.Error.Set(span, true) + } + + return result, err +} + +func (s *OpenTracingLayerRetentionPolicyStore) GetChannelPoliciesForUser(userID string, offset int, limit int) ([]*model.RetentionPolicyForChannel, error) { + origCtx := s.Root.Store.Context() + span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "RetentionPolicyStore.GetChannelPoliciesForUser") + s.Root.Store.SetContext(newCtx) + defer func() { + s.Root.Store.SetContext(origCtx) + }() + + defer span.Finish() + result, err := s.RetentionPolicyStore.GetChannelPoliciesForUser(userID, offset, limit) + if err != nil { + span.LogFields(spanlog.Error(err)) + ext.Error.Set(span, true) + } + + return result, err +} + +func (s *OpenTracingLayerRetentionPolicyStore) GetChannels(policyId string, offset int, limit int) (model.ChannelListWithTeamData, error) { + origCtx := s.Root.Store.Context() + span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "RetentionPolicyStore.GetChannels") + s.Root.Store.SetContext(newCtx) + defer func() { + s.Root.Store.SetContext(origCtx) + }() + + defer span.Finish() + result, err := s.RetentionPolicyStore.GetChannels(policyId, offset, limit) + if err != nil { + span.LogFields(spanlog.Error(err)) + ext.Error.Set(span, true) + } + + return result, err +} + +func (s *OpenTracingLayerRetentionPolicyStore) GetChannelsCount(policyId string) (int64, error) { + origCtx := s.Root.Store.Context() + span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "RetentionPolicyStore.GetChannelsCount") + s.Root.Store.SetContext(newCtx) + defer func() { + s.Root.Store.SetContext(origCtx) + }() + + defer span.Finish() + result, err := s.RetentionPolicyStore.GetChannelsCount(policyId) + if err != nil { + span.LogFields(spanlog.Error(err)) + ext.Error.Set(span, true) + } + + return result, err +} + +func (s *OpenTracingLayerRetentionPolicyStore) GetCount() (int64, error) { + origCtx := s.Root.Store.Context() + span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "RetentionPolicyStore.GetCount") + s.Root.Store.SetContext(newCtx) + defer func() { + s.Root.Store.SetContext(origCtx) + }() + + defer span.Finish() + result, err := s.RetentionPolicyStore.GetCount() + if err != nil { + span.LogFields(spanlog.Error(err)) + ext.Error.Set(span, true) + } + + return result, err +} + +func (s *OpenTracingLayerRetentionPolicyStore) GetTeamPoliciesCountForUser(userID string) (int64, error) { + origCtx := s.Root.Store.Context() + span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "RetentionPolicyStore.GetTeamPoliciesCountForUser") + s.Root.Store.SetContext(newCtx) + defer func() { + s.Root.Store.SetContext(origCtx) + }() + + defer span.Finish() + result, err := s.RetentionPolicyStore.GetTeamPoliciesCountForUser(userID) + if err != nil { + span.LogFields(spanlog.Error(err)) + ext.Error.Set(span, true) + } + + return result, err +} + +func (s *OpenTracingLayerRetentionPolicyStore) GetTeamPoliciesForUser(userID string, offset int, limit int) ([]*model.RetentionPolicyForTeam, error) { + origCtx := s.Root.Store.Context() + span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "RetentionPolicyStore.GetTeamPoliciesForUser") + s.Root.Store.SetContext(newCtx) + defer func() { + s.Root.Store.SetContext(origCtx) + }() + + defer span.Finish() + result, err := s.RetentionPolicyStore.GetTeamPoliciesForUser(userID, offset, limit) + if err != nil { + span.LogFields(spanlog.Error(err)) + ext.Error.Set(span, true) + } + + return result, err +} + +func (s *OpenTracingLayerRetentionPolicyStore) GetTeams(policyId string, offset int, limit int) ([]*model.Team, error) { + origCtx := s.Root.Store.Context() + span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "RetentionPolicyStore.GetTeams") + s.Root.Store.SetContext(newCtx) + defer func() { + s.Root.Store.SetContext(origCtx) + }() + + defer span.Finish() + result, err := s.RetentionPolicyStore.GetTeams(policyId, offset, limit) + if err != nil { + span.LogFields(spanlog.Error(err)) + ext.Error.Set(span, true) + } + + return result, err +} + +func (s *OpenTracingLayerRetentionPolicyStore) GetTeamsCount(policyId string) (int64, error) { + origCtx := s.Root.Store.Context() + span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "RetentionPolicyStore.GetTeamsCount") + s.Root.Store.SetContext(newCtx) + defer func() { + s.Root.Store.SetContext(origCtx) + }() + + defer span.Finish() + result, err := s.RetentionPolicyStore.GetTeamsCount(policyId) + if err != nil { + span.LogFields(spanlog.Error(err)) + ext.Error.Set(span, true) + } + + return result, err +} + +func (s *OpenTracingLayerRetentionPolicyStore) Patch(patch *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, error) { + origCtx := s.Root.Store.Context() + span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "RetentionPolicyStore.Patch") + s.Root.Store.SetContext(newCtx) + defer func() { + s.Root.Store.SetContext(origCtx) + }() + + defer span.Finish() + result, err := s.RetentionPolicyStore.Patch(patch) + if err != nil { + span.LogFields(spanlog.Error(err)) + ext.Error.Set(span, true) + } + + return result, err +} + +func (s *OpenTracingLayerRetentionPolicyStore) RemoveChannels(policyId string, channelIds []string) error { + origCtx := s.Root.Store.Context() + span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "RetentionPolicyStore.RemoveChannels") + s.Root.Store.SetContext(newCtx) + defer func() { + s.Root.Store.SetContext(origCtx) + }() + + defer span.Finish() + err := s.RetentionPolicyStore.RemoveChannels(policyId, channelIds) + if err != nil { + span.LogFields(spanlog.Error(err)) + ext.Error.Set(span, true) + } + + return err +} + +func (s *OpenTracingLayerRetentionPolicyStore) RemoveTeams(policyId string, teamIds []string) error { + origCtx := s.Root.Store.Context() + span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "RetentionPolicyStore.RemoveTeams") + s.Root.Store.SetContext(newCtx) + defer func() { + s.Root.Store.SetContext(origCtx) + }() + + defer span.Finish() + err := s.RetentionPolicyStore.RemoveTeams(policyId, teamIds) + if err != nil { + span.LogFields(spanlog.Error(err)) + ext.Error.Set(span, true) + } + + return err +} + +func (s *OpenTracingLayerRetentionPolicyStore) Save(policy *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, error) { + origCtx := s.Root.Store.Context() + span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "RetentionPolicyStore.Save") + s.Root.Store.SetContext(newCtx) + defer func() { + s.Root.Store.SetContext(origCtx) + }() + + defer span.Finish() + result, err := s.RetentionPolicyStore.Save(policy) + if err != nil { + span.LogFields(spanlog.Error(err)) + ext.Error.Set(span, true) + } + + return result, err +} + func (s *OpenTracingLayerRoleStore) AllChannelSchemeRoles() ([]*model.Role, error) { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "RoleStore.AllChannelSchemeRoles") @@ -7408,43 +7742,7 @@ func (s *OpenTracingLayerTeamStore) AnalyticsGetTeamCountForScheme(schemeID stri return result, err } -func (s *OpenTracingLayerTeamStore) AnalyticsPrivateTeamCount() (int64, error) { - origCtx := s.Root.Store.Context() - span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "TeamStore.AnalyticsPrivateTeamCount") - s.Root.Store.SetContext(newCtx) - defer func() { - s.Root.Store.SetContext(origCtx) - }() - - defer span.Finish() - result, err := s.TeamStore.AnalyticsPrivateTeamCount() - if err != nil { - span.LogFields(spanlog.Error(err)) - ext.Error.Set(span, true) - } - - return result, err -} - -func (s *OpenTracingLayerTeamStore) AnalyticsPublicTeamCount() (int64, error) { - origCtx := s.Root.Store.Context() - span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "TeamStore.AnalyticsPublicTeamCount") - s.Root.Store.SetContext(newCtx) - defer func() { - s.Root.Store.SetContext(origCtx) - }() - - defer span.Finish() - result, err := s.TeamStore.AnalyticsPublicTeamCount() - if err != nil { - span.LogFields(spanlog.Error(err)) - ext.Error.Set(span, true) - } - - return result, err -} - -func (s *OpenTracingLayerTeamStore) AnalyticsTeamCount(includeDeleted bool) (int64, error) { +func (s *OpenTracingLayerTeamStore) AnalyticsTeamCount(opts *model.TeamSearch) (int64, error) { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "TeamStore.AnalyticsTeamCount") s.Root.Store.SetContext(newCtx) @@ -7453,7 +7751,7 @@ func (s *OpenTracingLayerTeamStore) AnalyticsTeamCount(includeDeleted bool) (int }() defer span.Finish() - result, err := s.TeamStore.AnalyticsTeamCount(includeDeleted) + result, err := s.TeamStore.AnalyticsTeamCount(opts) if err != nil { span.LogFields(spanlog.Error(err)) ext.Error.Set(span, true) @@ -7565,7 +7863,7 @@ func (s *OpenTracingLayerTeamStore) GetAllForExportAfter(limit int, afterID stri return result, err } -func (s *OpenTracingLayerTeamStore) GetAllPage(offset int, limit int) ([]*model.Team, error) { +func (s *OpenTracingLayerTeamStore) GetAllPage(offset int, limit int, opts *model.TeamSearch) ([]*model.Team, error) { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "TeamStore.GetAllPage") s.Root.Store.SetContext(newCtx) @@ -7574,7 +7872,7 @@ func (s *OpenTracingLayerTeamStore) GetAllPage(offset int, limit int) ([]*model. }() defer span.Finish() - result, err := s.TeamStore.GetAllPage(offset, limit) + result, err := s.TeamStore.GetAllPage(offset, limit, opts) if err != nil { span.LogFields(spanlog.Error(err)) ext.Error.Set(span, true) @@ -7601,42 +7899,6 @@ func (s *OpenTracingLayerTeamStore) GetAllPrivateTeamListing() ([]*model.Team, e return result, err } -func (s *OpenTracingLayerTeamStore) GetAllPrivateTeamPageListing(offset int, limit int) ([]*model.Team, error) { - origCtx := s.Root.Store.Context() - span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "TeamStore.GetAllPrivateTeamPageListing") - s.Root.Store.SetContext(newCtx) - defer func() { - s.Root.Store.SetContext(origCtx) - }() - - defer span.Finish() - result, err := s.TeamStore.GetAllPrivateTeamPageListing(offset, limit) - if err != nil { - span.LogFields(spanlog.Error(err)) - ext.Error.Set(span, true) - } - - return result, err -} - -func (s *OpenTracingLayerTeamStore) GetAllPublicTeamPageListing(offset int, limit int) ([]*model.Team, error) { - origCtx := s.Root.Store.Context() - span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "TeamStore.GetAllPublicTeamPageListing") - s.Root.Store.SetContext(newCtx) - defer func() { - s.Root.Store.SetContext(origCtx) - }() - - defer span.Finish() - result, err := s.TeamStore.GetAllPublicTeamPageListing(offset, limit) - if err != nil { - span.LogFields(spanlog.Error(err)) - ext.Error.Set(span, true) - } - - return result, err -} - func (s *OpenTracingLayerTeamStore) GetAllTeamListing() ([]*model.Team, error) { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "TeamStore.GetAllTeamListing") @@ -7655,24 +7917,6 @@ func (s *OpenTracingLayerTeamStore) GetAllTeamListing() ([]*model.Team, error) { return result, err } -func (s *OpenTracingLayerTeamStore) GetAllTeamPageListing(offset int, limit int) ([]*model.Team, error) { - origCtx := s.Root.Store.Context() - span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "TeamStore.GetAllTeamPageListing") - s.Root.Store.SetContext(newCtx) - defer func() { - s.Root.Store.SetContext(origCtx) - }() - - defer span.Finish() - result, err := s.TeamStore.GetAllTeamPageListing(offset, limit) - if err != nil { - span.LogFields(spanlog.Error(err)) - ext.Error.Set(span, true) - } - - return result, err -} - func (s *OpenTracingLayerTeamStore) GetByInviteId(inviteID string) (*model.Team, error) { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "TeamStore.GetByInviteId") @@ -8154,7 +8398,7 @@ func (s *OpenTracingLayerTeamStore) SaveMultipleMembers(members []*model.TeamMem return result, err } -func (s *OpenTracingLayerTeamStore) SearchAll(term string, opts *model.TeamSearch) ([]*model.Team, error) { +func (s *OpenTracingLayerTeamStore) SearchAll(opts *model.TeamSearch) ([]*model.Team, error) { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "TeamStore.SearchAll") s.Root.Store.SetContext(newCtx) @@ -8163,7 +8407,7 @@ func (s *OpenTracingLayerTeamStore) SearchAll(term string, opts *model.TeamSearc }() defer span.Finish() - result, err := s.TeamStore.SearchAll(term, opts) + result, err := s.TeamStore.SearchAll(opts) if err != nil { span.LogFields(spanlog.Error(err)) ext.Error.Set(span, true) @@ -8172,7 +8416,7 @@ func (s *OpenTracingLayerTeamStore) SearchAll(term string, opts *model.TeamSearc return result, err } -func (s *OpenTracingLayerTeamStore) SearchAllPaged(term string, opts *model.TeamSearch) ([]*model.Team, int64, error) { +func (s *OpenTracingLayerTeamStore) SearchAllPaged(opts *model.TeamSearch) ([]*model.Team, int64, error) { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "TeamStore.SearchAllPaged") s.Root.Store.SetContext(newCtx) @@ -8181,7 +8425,7 @@ func (s *OpenTracingLayerTeamStore) SearchAllPaged(term string, opts *model.Team }() defer span.Finish() - result, resultVar1, err := s.TeamStore.SearchAllPaged(term, opts) + result, resultVar1, err := s.TeamStore.SearchAllPaged(opts) if err != nil { span.LogFields(spanlog.Error(err)) ext.Error.Set(span, true) @@ -8190,7 +8434,7 @@ func (s *OpenTracingLayerTeamStore) SearchAllPaged(term string, opts *model.Team return result, resultVar1, err } -func (s *OpenTracingLayerTeamStore) SearchOpen(term string) ([]*model.Team, error) { +func (s *OpenTracingLayerTeamStore) SearchOpen(opts *model.TeamSearch) ([]*model.Team, error) { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "TeamStore.SearchOpen") s.Root.Store.SetContext(newCtx) @@ -8199,7 +8443,7 @@ func (s *OpenTracingLayerTeamStore) SearchOpen(term string) ([]*model.Team, erro }() defer span.Finish() - result, err := s.TeamStore.SearchOpen(term) + result, err := s.TeamStore.SearchOpen(opts) if err != nil { span.LogFields(spanlog.Error(err)) ext.Error.Set(span, true) @@ -8208,7 +8452,7 @@ func (s *OpenTracingLayerTeamStore) SearchOpen(term string) ([]*model.Team, erro return result, err } -func (s *OpenTracingLayerTeamStore) SearchPrivate(term string) ([]*model.Team, error) { +func (s *OpenTracingLayerTeamStore) SearchPrivate(opts *model.TeamSearch) ([]*model.Team, error) { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "TeamStore.SearchPrivate") s.Root.Store.SetContext(newCtx) @@ -8217,7 +8461,7 @@ func (s *OpenTracingLayerTeamStore) SearchPrivate(term string) ([]*model.Team, e }() defer span.Finish() - result, err := s.TeamStore.SearchPrivate(term) + result, err := s.TeamStore.SearchPrivate(opts) if err != nil { span.LogFields(spanlog.Error(err)) ext.Error.Set(span, true) @@ -10888,6 +11132,7 @@ func New(childStore store.Store, ctx context.Context) *OpenTracingLayer { newStore.ProductNoticesStore = &OpenTracingLayerProductNoticesStore{ProductNoticesStore: childStore.ProductNotices(), Root: &newStore} newStore.ReactionStore = &OpenTracingLayerReactionStore{ReactionStore: childStore.Reaction(), Root: &newStore} newStore.RemoteClusterStore = &OpenTracingLayerRemoteClusterStore{RemoteClusterStore: childStore.RemoteCluster(), Root: &newStore} + newStore.RetentionPolicyStore = &OpenTracingLayerRetentionPolicyStore{RetentionPolicyStore: childStore.RetentionPolicy(), Root: &newStore} newStore.RoleStore = &OpenTracingLayerRoleStore{RoleStore: childStore.Role(), Root: &newStore} newStore.SchemeStore = &OpenTracingLayerSchemeStore{SchemeStore: childStore.Scheme(), Root: &newStore} newStore.SessionStore = &OpenTracingLayerSessionStore{SessionStore: childStore.Session(), Root: &newStore} diff --git a/store/retrylayer/retrylayer.go b/store/retrylayer/retrylayer.go index 36ef22531b..1157920b83 100644 --- a/store/retrylayer/retrylayer.go +++ b/store/retrylayer/retrylayer.go @@ -41,6 +41,7 @@ type RetryLayer struct { ProductNoticesStore store.ProductNoticesStore ReactionStore store.ReactionStore RemoteClusterStore store.RemoteClusterStore + RetentionPolicyStore store.RetentionPolicyStore RoleStore store.RoleStore SchemeStore store.SchemeStore SessionStore store.SessionStore @@ -142,6 +143,10 @@ func (s *RetryLayer) RemoteCluster() store.RemoteClusterStore { return s.RemoteClusterStore } +func (s *RetryLayer) RetentionPolicy() store.RetentionPolicyStore { + return s.RetentionPolicyStore +} + func (s *RetryLayer) Role() store.RoleStore { return s.RoleStore } @@ -307,6 +312,11 @@ type RetryLayerRemoteClusterStore struct { Root *RetryLayer } +type RetryLayerRetentionPolicyStore struct { + store.RetentionPolicyStore + Root *RetryLayer +} + type RetryLayerRoleStore struct { store.RoleStore Root *RetryLayer @@ -6600,6 +6610,366 @@ func (s *RetryLayerRemoteClusterStore) UpdateTopics(remoteClusterId string, topi } +func (s *RetryLayerRetentionPolicyStore) AddChannels(policyId string, channelIds []string) error { + + tries := 0 + for { + err := s.RetentionPolicyStore.AddChannels(policyId, channelIds) + if err == nil { + return nil + } + if !isRepeatableError(err) { + return err + } + tries++ + if tries >= 3 { + err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures") + return err + } + } + +} + +func (s *RetryLayerRetentionPolicyStore) AddTeams(policyId string, teamIds []string) error { + + tries := 0 + for { + err := s.RetentionPolicyStore.AddTeams(policyId, teamIds) + if err == nil { + return nil + } + if !isRepeatableError(err) { + return err + } + tries++ + if tries >= 3 { + err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures") + return err + } + } + +} + +func (s *RetryLayerRetentionPolicyStore) Delete(id string) error { + + tries := 0 + for { + err := s.RetentionPolicyStore.Delete(id) + if err == nil { + return nil + } + if !isRepeatableError(err) { + return err + } + tries++ + if tries >= 3 { + err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures") + return err + } + } + +} + +func (s *RetryLayerRetentionPolicyStore) Get(id string) (*model.RetentionPolicyWithTeamAndChannelCounts, error) { + + tries := 0 + for { + result, err := s.RetentionPolicyStore.Get(id) + if err == nil { + return result, nil + } + if !isRepeatableError(err) { + return result, err + } + tries++ + if tries >= 3 { + err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures") + return result, err + } + } + +} + +func (s *RetryLayerRetentionPolicyStore) GetAll(offset int, limit int) ([]*model.RetentionPolicyWithTeamAndChannelCounts, error) { + + tries := 0 + for { + result, err := s.RetentionPolicyStore.GetAll(offset, limit) + if err == nil { + return result, nil + } + if !isRepeatableError(err) { + return result, err + } + tries++ + if tries >= 3 { + err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures") + return result, err + } + } + +} + +func (s *RetryLayerRetentionPolicyStore) GetChannelPoliciesCountForUser(userID string) (int64, error) { + + tries := 0 + for { + result, err := s.RetentionPolicyStore.GetChannelPoliciesCountForUser(userID) + if err == nil { + return result, nil + } + if !isRepeatableError(err) { + return result, err + } + tries++ + if tries >= 3 { + err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures") + return result, err + } + } + +} + +func (s *RetryLayerRetentionPolicyStore) GetChannelPoliciesForUser(userID string, offset int, limit int) ([]*model.RetentionPolicyForChannel, error) { + + tries := 0 + for { + result, err := s.RetentionPolicyStore.GetChannelPoliciesForUser(userID, offset, limit) + if err == nil { + return result, nil + } + if !isRepeatableError(err) { + return result, err + } + tries++ + if tries >= 3 { + err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures") + return result, err + } + } + +} + +func (s *RetryLayerRetentionPolicyStore) GetChannels(policyId string, offset int, limit int) (model.ChannelListWithTeamData, error) { + + tries := 0 + for { + result, err := s.RetentionPolicyStore.GetChannels(policyId, offset, limit) + if err == nil { + return result, nil + } + if !isRepeatableError(err) { + return result, err + } + tries++ + if tries >= 3 { + err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures") + return result, err + } + } + +} + +func (s *RetryLayerRetentionPolicyStore) GetChannelsCount(policyId string) (int64, error) { + + tries := 0 + for { + result, err := s.RetentionPolicyStore.GetChannelsCount(policyId) + if err == nil { + return result, nil + } + if !isRepeatableError(err) { + return result, err + } + tries++ + if tries >= 3 { + err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures") + return result, err + } + } + +} + +func (s *RetryLayerRetentionPolicyStore) GetCount() (int64, error) { + + tries := 0 + for { + result, err := s.RetentionPolicyStore.GetCount() + if err == nil { + return result, nil + } + if !isRepeatableError(err) { + return result, err + } + tries++ + if tries >= 3 { + err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures") + return result, err + } + } + +} + +func (s *RetryLayerRetentionPolicyStore) GetTeamPoliciesCountForUser(userID string) (int64, error) { + + tries := 0 + for { + result, err := s.RetentionPolicyStore.GetTeamPoliciesCountForUser(userID) + if err == nil { + return result, nil + } + if !isRepeatableError(err) { + return result, err + } + tries++ + if tries >= 3 { + err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures") + return result, err + } + } + +} + +func (s *RetryLayerRetentionPolicyStore) GetTeamPoliciesForUser(userID string, offset int, limit int) ([]*model.RetentionPolicyForTeam, error) { + + tries := 0 + for { + result, err := s.RetentionPolicyStore.GetTeamPoliciesForUser(userID, offset, limit) + if err == nil { + return result, nil + } + if !isRepeatableError(err) { + return result, err + } + tries++ + if tries >= 3 { + err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures") + return result, err + } + } + +} + +func (s *RetryLayerRetentionPolicyStore) GetTeams(policyId string, offset int, limit int) ([]*model.Team, error) { + + tries := 0 + for { + result, err := s.RetentionPolicyStore.GetTeams(policyId, offset, limit) + if err == nil { + return result, nil + } + if !isRepeatableError(err) { + return result, err + } + tries++ + if tries >= 3 { + err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures") + return result, err + } + } + +} + +func (s *RetryLayerRetentionPolicyStore) GetTeamsCount(policyId string) (int64, error) { + + tries := 0 + for { + result, err := s.RetentionPolicyStore.GetTeamsCount(policyId) + if err == nil { + return result, nil + } + if !isRepeatableError(err) { + return result, err + } + tries++ + if tries >= 3 { + err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures") + return result, err + } + } + +} + +func (s *RetryLayerRetentionPolicyStore) Patch(patch *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, error) { + + tries := 0 + for { + result, err := s.RetentionPolicyStore.Patch(patch) + if err == nil { + return result, nil + } + if !isRepeatableError(err) { + return result, err + } + tries++ + if tries >= 3 { + err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures") + return result, err + } + } + +} + +func (s *RetryLayerRetentionPolicyStore) RemoveChannels(policyId string, channelIds []string) error { + + tries := 0 + for { + err := s.RetentionPolicyStore.RemoveChannels(policyId, channelIds) + if err == nil { + return nil + } + if !isRepeatableError(err) { + return err + } + tries++ + if tries >= 3 { + err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures") + return err + } + } + +} + +func (s *RetryLayerRetentionPolicyStore) RemoveTeams(policyId string, teamIds []string) error { + + tries := 0 + for { + err := s.RetentionPolicyStore.RemoveTeams(policyId, teamIds) + if err == nil { + return nil + } + if !isRepeatableError(err) { + return err + } + tries++ + if tries >= 3 { + err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures") + return err + } + } + +} + +func (s *RetryLayerRetentionPolicyStore) Save(policy *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, error) { + + tries := 0 + for { + result, err := s.RetentionPolicyStore.Save(policy) + if err == nil { + return result, nil + } + if !isRepeatableError(err) { + return result, err + } + tries++ + if tries >= 3 { + err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures") + return result, err + } + } + +} + func (s *RetryLayerRoleStore) AllChannelSchemeRoles() ([]*model.Role, error) { tries := 0 @@ -8046,51 +8416,11 @@ func (s *RetryLayerTeamStore) AnalyticsGetTeamCountForScheme(schemeID string) (i } -func (s *RetryLayerTeamStore) AnalyticsPrivateTeamCount() (int64, error) { +func (s *RetryLayerTeamStore) AnalyticsTeamCount(opts *model.TeamSearch) (int64, error) { tries := 0 for { - result, err := s.TeamStore.AnalyticsPrivateTeamCount() - if err == nil { - return result, nil - } - if !isRepeatableError(err) { - return result, err - } - tries++ - if tries >= 3 { - err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures") - return result, err - } - } - -} - -func (s *RetryLayerTeamStore) AnalyticsPublicTeamCount() (int64, error) { - - tries := 0 - for { - result, err := s.TeamStore.AnalyticsPublicTeamCount() - if err == nil { - return result, nil - } - if !isRepeatableError(err) { - return result, err - } - tries++ - if tries >= 3 { - err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures") - return result, err - } - } - -} - -func (s *RetryLayerTeamStore) AnalyticsTeamCount(includeDeleted bool) (int64, error) { - - tries := 0 - for { - result, err := s.TeamStore.AnalyticsTeamCount(includeDeleted) + result, err := s.TeamStore.AnalyticsTeamCount(opts) if err == nil { return result, nil } @@ -8212,11 +8542,11 @@ func (s *RetryLayerTeamStore) GetAllForExportAfter(limit int, afterID string) ([ } -func (s *RetryLayerTeamStore) GetAllPage(offset int, limit int) ([]*model.Team, error) { +func (s *RetryLayerTeamStore) GetAllPage(offset int, limit int, opts *model.TeamSearch) ([]*model.Team, error) { tries := 0 for { - result, err := s.TeamStore.GetAllPage(offset, limit) + result, err := s.TeamStore.GetAllPage(offset, limit, opts) if err == nil { return result, nil } @@ -8252,46 +8582,6 @@ func (s *RetryLayerTeamStore) GetAllPrivateTeamListing() ([]*model.Team, error) } -func (s *RetryLayerTeamStore) GetAllPrivateTeamPageListing(offset int, limit int) ([]*model.Team, error) { - - tries := 0 - for { - result, err := s.TeamStore.GetAllPrivateTeamPageListing(offset, limit) - if err == nil { - return result, nil - } - if !isRepeatableError(err) { - return result, err - } - tries++ - if tries >= 3 { - err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures") - return result, err - } - } - -} - -func (s *RetryLayerTeamStore) GetAllPublicTeamPageListing(offset int, limit int) ([]*model.Team, error) { - - tries := 0 - for { - result, err := s.TeamStore.GetAllPublicTeamPageListing(offset, limit) - if err == nil { - return result, nil - } - if !isRepeatableError(err) { - return result, err - } - tries++ - if tries >= 3 { - err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures") - return result, err - } - } - -} - func (s *RetryLayerTeamStore) GetAllTeamListing() ([]*model.Team, error) { tries := 0 @@ -8312,26 +8602,6 @@ func (s *RetryLayerTeamStore) GetAllTeamListing() ([]*model.Team, error) { } -func (s *RetryLayerTeamStore) GetAllTeamPageListing(offset int, limit int) ([]*model.Team, error) { - - tries := 0 - for { - result, err := s.TeamStore.GetAllTeamPageListing(offset, limit) - if err == nil { - return result, nil - } - if !isRepeatableError(err) { - return result, err - } - tries++ - if tries >= 3 { - err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures") - return result, err - } - } - -} - func (s *RetryLayerTeamStore) GetByInviteId(inviteID string) (*model.Team, error) { tries := 0 @@ -8858,11 +9128,11 @@ func (s *RetryLayerTeamStore) SaveMultipleMembers(members []*model.TeamMember, m } -func (s *RetryLayerTeamStore) SearchAll(term string, opts *model.TeamSearch) ([]*model.Team, error) { +func (s *RetryLayerTeamStore) SearchAll(opts *model.TeamSearch) ([]*model.Team, error) { tries := 0 for { - result, err := s.TeamStore.SearchAll(term, opts) + result, err := s.TeamStore.SearchAll(opts) if err == nil { return result, nil } @@ -8878,11 +9148,11 @@ func (s *RetryLayerTeamStore) SearchAll(term string, opts *model.TeamSearch) ([] } -func (s *RetryLayerTeamStore) SearchAllPaged(term string, opts *model.TeamSearch) ([]*model.Team, int64, error) { +func (s *RetryLayerTeamStore) SearchAllPaged(opts *model.TeamSearch) ([]*model.Team, int64, error) { tries := 0 for { - result, resultVar1, err := s.TeamStore.SearchAllPaged(term, opts) + result, resultVar1, err := s.TeamStore.SearchAllPaged(opts) if err == nil { return result, resultVar1, nil } @@ -8898,11 +9168,11 @@ func (s *RetryLayerTeamStore) SearchAllPaged(term string, opts *model.TeamSearch } -func (s *RetryLayerTeamStore) SearchOpen(term string) ([]*model.Team, error) { +func (s *RetryLayerTeamStore) SearchOpen(opts *model.TeamSearch) ([]*model.Team, error) { tries := 0 for { - result, err := s.TeamStore.SearchOpen(term) + result, err := s.TeamStore.SearchOpen(opts) if err == nil { return result, nil } @@ -8918,11 +9188,11 @@ func (s *RetryLayerTeamStore) SearchOpen(term string) ([]*model.Team, error) { } -func (s *RetryLayerTeamStore) SearchPrivate(term string) ([]*model.Team, error) { +func (s *RetryLayerTeamStore) SearchPrivate(opts *model.TeamSearch) ([]*model.Team, error) { tries := 0 for { - result, err := s.TeamStore.SearchPrivate(term) + result, err := s.TeamStore.SearchPrivate(opts) if err == nil { return result, nil } @@ -11804,6 +12074,7 @@ func New(childStore store.Store) *RetryLayer { newStore.ProductNoticesStore = &RetryLayerProductNoticesStore{ProductNoticesStore: childStore.ProductNotices(), Root: &newStore} newStore.ReactionStore = &RetryLayerReactionStore{ReactionStore: childStore.Reaction(), Root: &newStore} newStore.RemoteClusterStore = &RetryLayerRemoteClusterStore{RemoteClusterStore: childStore.RemoteCluster(), Root: &newStore} + newStore.RetentionPolicyStore = &RetryLayerRetentionPolicyStore{RetentionPolicyStore: childStore.RetentionPolicy(), Root: &newStore} newStore.RoleStore = &RetryLayerRoleStore{RoleStore: childStore.Role(), Root: &newStore} newStore.SchemeStore = &RetryLayerSchemeStore{SchemeStore: childStore.Scheme(), Root: &newStore} newStore.SessionStore = &RetryLayerSessionStore{SessionStore: childStore.Session(), Root: &newStore} diff --git a/store/retrylayer/retrylayer_test.go b/store/retrylayer/retrylayer_test.go index 77081f32cf..eeaca93187 100644 --- a/store/retrylayer/retrylayer_test.go +++ b/store/retrylayer/retrylayer_test.go @@ -40,6 +40,7 @@ func genStore() *mocks.Store { mock.On("Preference").Return(&mocks.PreferenceStore{}) mock.On("ProductNotices").Return(&mocks.ProductNoticesStore{}) mock.On("Reaction").Return(&mocks.ReactionStore{}) + mock.On("RetentionPolicy").Return(&mocks.RetentionPolicyStore{}) mock.On("Role").Return(&mocks.RoleStore{}) mock.On("Scheme").Return(&mocks.SchemeStore{}) mock.On("Session").Return(&mocks.SessionStore{}) diff --git a/store/sqlstore/channel_store.go b/store/sqlstore/channel_store.go index c1ba385515..334612b04a 100644 --- a/store/sqlstore/channel_store.go +++ b/store/sqlstore/channel_store.go @@ -1036,6 +1036,9 @@ func (s SqlChannelStore) getAllChannelsQuery(opts store.ChannelSearchOpts, forCo selectStr = "count(c.Id)" } else { selectStr = "c.*, Teams.DisplayName AS TeamDisplayName, Teams.Name AS TeamName, Teams.UpdateAt AS TeamUpdateAt" + if opts.IncludePolicyID { + selectStr += ", RetentionPoliciesChannels.PolicyId" + } } query := s.getQueryBuilder(). @@ -1059,6 +1062,13 @@ func (s SqlChannelStore) getAllChannelsQuery(opts store.ChannelSearchOpts, forCo query = query.Where(sq.NotEq{"c.Name": opts.ExcludeChannelNames}) } + if opts.ExcludePolicyConstrained || opts.IncludePolicyID { + query = query.LeftJoin("RetentionPoliciesChannels ON c.Id = RetentionPoliciesChannels.ChannelId") + } + if opts.ExcludePolicyConstrained { + query = query.Where("RetentionPoliciesChannels.ChannelId IS NULL") + } + return query } @@ -2660,7 +2670,7 @@ func (s SqlChannelStore) SearchForUserInTeam(userId string, teamId string, term }) } -func (s SqlChannelStore) channelSearchQuery(term string, opts store.ChannelSearchOpts, countQuery bool) sq.SelectBuilder { +func (s SqlChannelStore) channelSearchQuery(opts *store.ChannelSearchOpts) sq.SelectBuilder { var limit int if opts.PerPage != nil { limit = *opts.PerPage @@ -2669,10 +2679,16 @@ func (s SqlChannelStore) channelSearchQuery(term string, opts store.ChannelSearc } var selectStr string - if countQuery { + if opts.CountOnly { selectStr = "count(*)" } else { - selectStr = "c.*, t.DisplayName AS TeamDisplayName, t.Name AS TeamName, t.UpdateAt as TeamUpdateAt" + selectStr = "c.*" + if opts.IncludeTeamInfo { + selectStr += ", t.DisplayName AS TeamDisplayName, t.Name AS TeamName, t.UpdateAt as TeamUpdateAt" + } + if opts.IncludePolicyID { + selectStr += ", RetentionPoliciesChannels.PolicyId" + } } query := s.getQueryBuilder(). @@ -2681,7 +2697,7 @@ func (s SqlChannelStore) channelSearchQuery(term string, opts store.ChannelSearc Join("Teams AS t ON t.Id = c.TeamId") // don't bother ordering or limiting if we're just getting the count - if !countQuery { + if !opts.CountOnly { query = query. OrderBy("c.DisplayName, t.DisplayName"). Limit(uint64(limit)) @@ -2692,14 +2708,27 @@ func (s SqlChannelStore) channelSearchQuery(term string, opts store.ChannelSearc query = query.Where(sq.Eq{"c.DeleteAt": int(0)}) } - if opts.IsPaginated() && !countQuery { + if opts.IsPaginated() && !opts.CountOnly { query = query.Offset(uint64(*opts.Page * *opts.PerPage)) } - likeClause, likeTerm := s.buildLIKEClause(term, "c.Name, c.DisplayName, c.Purpose") + if opts.PolicyID != "" { + query = query. + InnerJoin("RetentionPoliciesChannels ON c.Id = RetentionPoliciesChannels.ChannelId"). + Where(sq.Eq{"RetentionPoliciesChannels.PolicyId": opts.PolicyID}) + } else if opts.ExcludePolicyConstrained { + query = query. + LeftJoin("RetentionPoliciesChannels ON c.Id = RetentionPoliciesChannels.ChannelId"). + Where("RetentionPoliciesChannels.ChannelId IS NULL") + } else if opts.IncludePolicyID { + query = query. + LeftJoin("RetentionPoliciesChannels ON c.Id = RetentionPoliciesChannels.ChannelId") + } + + likeClause, likeTerm := s.buildLIKEClause(opts.Term, "c.Name, c.DisplayName, c.Purpose") if likeTerm != "" { likeClause = strings.ReplaceAll(likeClause, ":LikeTerm", "?") - fulltextClause, fulltextTerm := s.buildFulltextClause(term, "c.Name, c.DisplayName, c.Purpose") + fulltextClause, fulltextTerm := s.buildFulltextClause(opts.Term, "c.Name, c.DisplayName, c.Purpose") fulltextClause = strings.ReplaceAll(fulltextClause, ":FulltextTerm", "?") query = query.Where(sq.Or{ sq.Expr(likeClause, likeTerm, likeTerm, likeTerm), // Keep the number of likeTerms same as the number @@ -2730,7 +2759,7 @@ func (s SqlChannelStore) channelSearchQuery(term string, opts store.ChannelSearc } if opts.Public && !opts.Private { - query = query.Where(sq.Eq{"c.Type": model.CHANNEL_OPEN}) + query = query.InnerJoin("PublicChannels ON c.Id = PublicChannels.Id") } else if opts.Private && !opts.Public { query = query.Where(sq.Eq{"c.Type": model.CHANNEL_PRIVATE}) } else { @@ -2744,7 +2773,9 @@ func (s SqlChannelStore) channelSearchQuery(term string, opts store.ChannelSearc } func (s SqlChannelStore) SearchAllChannels(term string, opts store.ChannelSearchOpts) (*model.ChannelListWithTeamData, int64, error) { - queryString, args, err := s.channelSearchQuery(term, opts, false).ToSql() + opts.Term = term + opts.IncludeTeamInfo = true + queryString, args, err := s.channelSearchQuery(&opts).ToSql() if err != nil { return nil, 0, errors.Wrap(err, "channel_tosql") } @@ -2757,7 +2788,8 @@ func (s SqlChannelStore) SearchAllChannels(term string, opts store.ChannelSearch // only query a 2nd time for the count if the results are being requested paginated. if opts.IsPaginated() { - queryString, args, err = s.channelSearchQuery(term, opts, true).ToSql() + opts.CountOnly = true + queryString, args, err = s.channelSearchQuery(&opts).ToSql() if err != nil { return nil, 0, errors.Wrap(err, "channel_tosql") } diff --git a/store/sqlstore/channel_store_test.go b/store/sqlstore/channel_store_test.go index 86660460e2..06ed53599f 100644 --- a/store/sqlstore/channel_store_test.go +++ b/store/sqlstore/channel_store_test.go @@ -31,8 +31,8 @@ func TestChannelSearchQuerySQLInjection(t *testing.T) { SqlStore: st.SqlStore, } - opts := store.ChannelSearchOpts{} - builder := s.channelSearchQuery("'or'1'=sleep(3))); -- -", opts, false) + opts := store.ChannelSearchOpts{Term: "'or'1'=sleep(3))); -- -"} + builder := s.channelSearchQuery(&opts) query, _, err := builder.ToSql() require.NoError(t, err) assert.NotContains(t, query, "sleep") diff --git a/store/sqlstore/retention_policy_store.go b/store/sqlstore/retention_policy_store.go new file mode 100644 index 0000000000..60ef26d19a --- /dev/null +++ b/store/sqlstore/retention_policy_store.go @@ -0,0 +1,609 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +package sqlstore + +import ( + "database/sql" + + sq "github.com/Masterminds/squirrel" + "github.com/go-sql-driver/mysql" + "github.com/lib/pq" + "github.com/mattermost/gorp" + "github.com/mattermost/mattermost-server/v5/einterfaces" + "github.com/mattermost/mattermost-server/v5/model" + "github.com/mattermost/mattermost-server/v5/store" + "github.com/pkg/errors" +) + +type SqlRetentionPolicyStore struct { + *SqlStore + metrics einterfaces.MetricsInterface +} + +func newSqlRetentionPolicyStore(sqlStore *SqlStore, metrics einterfaces.MetricsInterface) store.RetentionPolicyStore { + s := &SqlRetentionPolicyStore{ + SqlStore: sqlStore, + metrics: metrics, + } + + for _, db := range sqlStore.GetAllConns() { + table := db.AddTableWithName(model.RetentionPolicy{}, "RetentionPolicies") + table.SetKeys(false, "Id") + table.ColMap("Id").SetMaxSize(26) + table.ColMap("DisplayName").SetMaxSize(64) + + tableC := db.AddTableWithName(model.RetentionPolicyChannel{}, "RetentionPoliciesChannels") + tableC.SetKeys(false, "ChannelId") + tableC.ColMap("PolicyId").SetMaxSize(26) + tableC.ColMap("ChannelId").SetMaxSize(26) + + tableT := db.AddTableWithName(model.RetentionPolicyTeam{}, "RetentionPoliciesTeams") + tableT.SetKeys(false, "TeamId") + tableT.ColMap("PolicyId").SetMaxSize(26) + tableT.ColMap("TeamId").SetMaxSize(26) + } + + return s +} + +func (s *SqlRetentionPolicyStore) createIndexesIfNotExists() { + s.CreateCompositeIndexIfNotExists("IDX_RetentionPolicies_DisplayName_Id", "RetentionPolicies", + []string{"DisplayName", "Id"}) + s.CreateIndexIfNotExists("IDX_RetentionPoliciesChannels_PolicyId", "RetentionPoliciesChannels", "PolicyId") + s.CreateIndexIfNotExists("IDX_RetentionPoliciesTeams_PolicyId", "RetentionPoliciesTeams", "PolicyId") + s.CreateForeignKeyIfNotExists("RetentionPoliciesChannels", "PolicyId", "RetentionPolicies", "Id", true) + s.CreateForeignKeyIfNotExists("RetentionPoliciesTeams", "PolicyId", "RetentionPolicies", "Id", true) +} + +// executePossiblyEmptyQuery only executes the query if it is non-empty. This helps avoid +// having to check for MySQL, which, unlike Postgres, does not allow empty queries. +func executePossiblyEmptyQuery(txn *gorp.Transaction, query string, args ...interface{}) (sql.Result, error) { + if query == "" { + return nil, nil + } + return txn.Exec(query, args...) +} + +func (s *SqlRetentionPolicyStore) Save(policy *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, error) { + // Strategy: + // 1. Insert new policy + // 2. Insert new channels into policy + // 3. Insert new teams into policy + + if err := s.checkTeamsExist(policy.TeamIDs); err != nil { + return nil, err + } + if err := s.checkChannelsExist(policy.ChannelIDs); err != nil { + return nil, err + } + + policy.ID = model.NewId() + + policyInsertQuery, policyInsertArgs, err := s.getQueryBuilder(). + Insert("RetentionPolicies"). + Columns("Id", "DisplayName", "PostDuration"). + Values(policy.ID, policy.DisplayName, policy.PostDuration). + ToSql() + if err != nil { + return nil, err + } + + channelsInsertQuery, channelsInsertArgs, err := s.buildInsertRetentionPoliciesChannelsQuery(policy.ID, policy.ChannelIDs) + if err != nil { + return nil, err + } + + teamsInsertQuery, teamsInsertArgs, err := s.buildInsertRetentionPoliciesTeamsQuery(policy.ID, policy.TeamIDs) + if err != nil { + return nil, err + } + + policySelectQuery, policySelectProps := s.buildGetPolicyQuery(policy.ID) + + txn, err := s.GetMaster().Begin() + if err != nil { + return nil, err + } + defer finalizeTransaction(txn) + // Create a new policy in RetentionPolicies + if _, err = txn.Exec(policyInsertQuery, policyInsertArgs...); err != nil { + return nil, err + } + // Insert the channel IDs into RetentionPoliciesChannels + if _, err = executePossiblyEmptyQuery(txn, channelsInsertQuery, channelsInsertArgs...); err != nil { + return nil, err + } + // Insert the team IDs into RetentionPoliciesTeams + if _, err = executePossiblyEmptyQuery(txn, teamsInsertQuery, teamsInsertArgs...); err != nil { + return nil, err + } + // Select the new policy (with team/channel counts) which we just created + var newPolicy model.RetentionPolicyWithTeamAndChannelCounts + if err = txn.SelectOne(&newPolicy, policySelectQuery, policySelectProps); err != nil { + return nil, err + } + if err = txn.Commit(); err != nil { + return nil, err + } + return &newPolicy, nil +} + +func (s *SqlRetentionPolicyStore) checkTeamsExist(teamIDs []string) error { + if len(teamIDs) > 0 { + teamsSelectQuery, teamsSelectArgs, err := s.getQueryBuilder(). + Select("Id"). + From("Teams"). + Where(sq.Eq{"Id": teamIDs}). + ToSql() + if err != nil { + return err + } + var rows []*string + _, err = s.GetReplica().Select(&rows, teamsSelectQuery, teamsSelectArgs...) + if err != nil { + return err + } + if len(rows) == len(teamIDs) { + return nil + } + retrievedIDs := make(map[string]bool) + for _, teamID := range rows { + retrievedIDs[*teamID] = true + } + for _, teamID := range teamIDs { + if _, ok := retrievedIDs[teamID]; !ok { + return store.NewErrNotFound("Team", teamID) + } + } + } + return nil +} + +func (s *SqlRetentionPolicyStore) checkChannelsExist(channelIDs []string) error { + if len(channelIDs) > 0 { + channelsSelectQuery, channelsSelectArgs, err := s.getQueryBuilder(). + Select("Id"). + From("Channels"). + Where(sq.Eq{"Id": channelIDs}). + ToSql() + if err != nil { + return err + } + var rows []*string + _, err = s.GetReplica().Select(&rows, channelsSelectQuery, channelsSelectArgs...) + if err != nil { + return err + } + if len(rows) == len(channelIDs) { + return nil + } + retrievedIDs := make(map[string]bool) + for _, channelID := range rows { + retrievedIDs[*channelID] = true + } + for _, channelID := range channelIDs { + if _, ok := retrievedIDs[channelID]; !ok { + return store.NewErrNotFound("Channel", channelID) + } + } + } + return nil +} + +func (s *SqlRetentionPolicyStore) buildInsertRetentionPoliciesChannelsQuery(policyID string, channelIDs []string) (query string, args []interface{}, err error) { + if len(channelIDs) > 0 { + builder := s.getQueryBuilder(). + Insert("RetentionPoliciesChannels"). + Columns("PolicyId", "ChannelId") + for _, channelID := range channelIDs { + builder = builder.Values(policyID, channelID) + } + query, args, err = builder.ToSql() + } + return +} + +func (s *SqlRetentionPolicyStore) buildInsertRetentionPoliciesTeamsQuery(policyID string, teamIDs []string) (query string, args []interface{}, err error) { + if len(teamIDs) > 0 { + builder := s.getQueryBuilder(). + Insert("RetentionPoliciesTeams"). + Columns("PolicyId", "TeamId") + for _, teamID := range teamIDs { + builder = builder.Values(policyID, teamID) + } + query, args, err = builder.ToSql() + } + return +} + +func (s *SqlRetentionPolicyStore) Patch(patch *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, error) { + // Strategy: + // 1. Update policy attributes + // 2. Delete existing channels from policy + // 3. Insert new channels into policy + // 4. Delete existing teams from policy + // 5. Insert new teams into policy + // 6. Read new policy + + var err error + if err = s.checkTeamsExist(patch.TeamIDs); err != nil { + return nil, err + } + if err = s.checkChannelsExist(patch.ChannelIDs); err != nil { + return nil, err + } + + policyUpdateQuery := "" + policyUpdateArgs := []interface{}{} + if patch.DisplayName != "" || patch.PostDuration != nil { + builder := s.getQueryBuilder().Update("RetentionPolicies") + if patch.DisplayName != "" { + builder = builder.Set("DisplayName", patch.DisplayName) + } + if patch.PostDuration != nil { + builder = builder.Set("PostDuration", *patch.PostDuration) + } + policyUpdateQuery, policyUpdateArgs, err = builder. + Where(sq.Eq{"Id": patch.ID}). + ToSql() + if err != nil { + return nil, err + } + } + + channelsDeleteQuery := "" + channelsDeleteArgs := []interface{}{} + channelsInsertQuery := "" + channelsInsertArgs := []interface{}{} + if patch.ChannelIDs != nil { + channelsDeleteQuery, channelsDeleteArgs, err = s.getQueryBuilder(). + Delete("RetentionPoliciesChannels"). + Where(sq.Eq{"PolicyId": patch.ID}). + ToSql() + if err != nil { + return nil, err + } + + channelsInsertQuery, channelsInsertArgs, err = s.buildInsertRetentionPoliciesChannelsQuery(patch.ID, patch.ChannelIDs) + if err != nil { + return nil, err + } + } + + teamsDeleteQuery := "" + teamsDeleteArgs := []interface{}{} + teamsInsertQuery := "" + teamsInsertArgs := []interface{}{} + if patch.TeamIDs != nil { + teamsDeleteQuery, teamsDeleteArgs, err = s.getQueryBuilder(). + Delete("RetentionPoliciesTeams"). + Where(sq.Eq{"PolicyId": patch.ID}). + ToSql() + if err != nil { + return nil, err + } + + teamsInsertQuery, teamsInsertArgs, err = s.buildInsertRetentionPoliciesTeamsQuery(patch.ID, patch.TeamIDs) + if err != nil { + return nil, err + } + } + + policySelectQuery, policySelectProps := s.buildGetPolicyQuery(patch.ID) + + txn, err := s.GetMaster().Begin() + if err != nil { + return nil, err + } + defer finalizeTransaction(txn) + // Update the fields of the policy in RetentionPolicies + if _, err = executePossiblyEmptyQuery(txn, policyUpdateQuery, policyUpdateArgs...); err != nil { + return nil, err + } + // Remove all channels from the policy in RetentionPoliciesChannels + if _, err = executePossiblyEmptyQuery(txn, channelsDeleteQuery, channelsDeleteArgs...); err != nil { + return nil, err + } + // Insert the new channels for the policy in RetentionPoliciesChannels + if _, err = executePossiblyEmptyQuery(txn, channelsInsertQuery, channelsInsertArgs...); err != nil { + return nil, err + } + // Remove all teams from the policy in RetentionPoliciesTeams + if _, err = executePossiblyEmptyQuery(txn, teamsDeleteQuery, teamsDeleteArgs...); err != nil { + return nil, err + } + // Insert the new teams for the policy in RetentionPoliciesTeams + if _, err = executePossiblyEmptyQuery(txn, teamsInsertQuery, teamsInsertArgs...); err != nil { + return nil, err + } + // Select the policy which we just updated + var newPolicy model.RetentionPolicyWithTeamAndChannelCounts + if err = txn.SelectOne(&newPolicy, policySelectQuery, policySelectProps); err != nil { + return nil, err + } + if err = txn.Commit(); err != nil { + return nil, err + } + return &newPolicy, nil +} + +func (s *SqlRetentionPolicyStore) buildGetPolicyQuery(id string) (query string, props map[string]interface{}) { + return s.buildGetPoliciesQuery(id, 0, 1) +} + +// buildGetPoliciesQuery builds a query to select information for the policy with the specified +// ID, or, if `id` is the empty string, from all policies. The results returned will be sorted by +// policy display name and ID. +func (s *SqlRetentionPolicyStore) buildGetPoliciesQuery(id string, offset, limit int) (query string, props map[string]interface{}) { + props = map[string]interface{}{"Offset": offset, "Limit": limit} + whereIdEqualsPolicyId := "" + if id != "" { + whereIdEqualsPolicyId = "WHERE RetentionPolicies.Id = :PolicyId" + props["PolicyId"] = id + } + query = ` + SELECT RetentionPolicies.Id, + RetentionPolicies.DisplayName, + RetentionPolicies.PostDuration, + A.Count AS ChannelCount, + B.Count AS TeamCount + FROM RetentionPolicies + INNER JOIN ( + SELECT RetentionPolicies.Id, + COUNT(RetentionPoliciesChannels.ChannelId) AS Count + FROM RetentionPolicies + LEFT JOIN RetentionPoliciesChannels ON RetentionPolicies.Id = RetentionPoliciesChannels.PolicyId + ` + whereIdEqualsPolicyId + ` + GROUP BY RetentionPolicies.Id + ORDER BY RetentionPolicies.DisplayName, RetentionPolicies.Id + LIMIT :Limit + OFFSET :Offset + ) AS A ON RetentionPolicies.Id = A.Id + INNER JOIN ( + SELECT RetentionPolicies.Id, + COUNT(RetentionPoliciesTeams.TeamId) AS Count + FROM RetentionPolicies + LEFT JOIN RetentionPoliciesTeams ON RetentionPolicies.Id = RetentionPoliciesTeams.PolicyId + ` + whereIdEqualsPolicyId + ` + GROUP BY RetentionPolicies.Id + ORDER BY RetentionPolicies.DisplayName, RetentionPolicies.Id + LIMIT :Limit + OFFSET :Offset + ) AS B ON RetentionPolicies.Id = B.Id + ORDER BY RetentionPolicies.DisplayName, RetentionPolicies.Id` + return +} + +func (s *SqlRetentionPolicyStore) Get(id string) (*model.RetentionPolicyWithTeamAndChannelCounts, error) { + query, props := s.buildGetPolicyQuery(id) + var policy model.RetentionPolicyWithTeamAndChannelCounts + if err := s.GetReplica().SelectOne(&policy, query, props); err != nil { + return nil, err + } + return &policy, nil +} + +func (s *SqlRetentionPolicyStore) GetAll(offset, limit int) (policies []*model.RetentionPolicyWithTeamAndChannelCounts, err error) { + query, props := s.buildGetPoliciesQuery("", offset, limit) + _, err = s.GetReplica().Select(&policies, query, props) + return +} + +func (s *SqlRetentionPolicyStore) GetCount() (int64, error) { + return s.GetReplica().SelectInt("SELECT COUNT(*) FROM RetentionPolicies") +} + +func (s *SqlRetentionPolicyStore) Delete(id string) error { + builder := s.getQueryBuilder(). + Delete("RetentionPolicies"). + Where(sq.Eq{"Id": id}) + result, err := builder.RunWith(s.GetMaster()).Exec() + if err != nil { + return err + } + numRowsAffected, err := result.RowsAffected() + if err != nil { + return err + } else if numRowsAffected == 0 { + return errors.New("policy not found") + } + return nil +} + +func (s *SqlRetentionPolicyStore) GetChannels(policyId string, offset, limit int) (channels model.ChannelListWithTeamData, err error) { + const query = ` + SELECT Channels.*, + Teams.DisplayName AS TeamDisplayName, + Teams.Name AS TeamName, + Teams.UpdateAt AS TeamUpdateAt + FROM RetentionPoliciesChannels + INNER JOIN Channels ON RetentionPoliciesChannels.ChannelId = Channels.Id + INNER JOIN Teams ON Channels.TeamId = Teams.Id + WHERE RetentionPoliciesChannels.PolicyId = :PolicyId + ORDER BY Channels.DisplayName, Channels.Id + LIMIT :Limit + OFFSET :Offset` + props := map[string]interface{}{"PolicyId": policyId, "Limit": limit, "Offset": offset} + _, err = s.GetReplica().Select(&channels, query, props) + for _, channel := range channels { + channel.PolicyID = model.NewString(policyId) + } + return +} + +func (s *SqlRetentionPolicyStore) GetChannelsCount(policyId string) (int64, error) { + const query = ` + SELECT COUNT(*) + FROM RetentionPolicies + INNER JOIN RetentionPoliciesChannels ON RetentionPolicies.Id = RetentionPoliciesChannels.PolicyId + WHERE RetentionPolicies.Id = :PolicyId` + props := map[string]interface{}{"PolicyId": policyId} + return s.GetReplica().SelectInt(query, props) +} + +func (s *SqlRetentionPolicyStore) AddChannels(policyId string, channelIds []string) error { + if len(channelIds) == 0 { + return nil + } + if err := s.checkChannelsExist(channelIds); err != nil { + return err + } + builder := s.getQueryBuilder(). + Insert("RetentionPoliciesChannels"). + Columns("policyId", "channelId") + for _, channelId := range channelIds { + builder = builder.Values(policyId, channelId) + } + _, err := builder.RunWith(s.GetMaster()).Exec() + if err != nil { + switch dbErr := err.(type) { + case *pq.Error: + if dbErr.Code == PGForeignKeyViolationErrorCode { + return store.NewErrNotFound("RetentionPolicy", policyId) + } + case *mysql.MySQLError: + if dbErr.Number == MySQLForeignKeyViolationErrorCode { + return store.NewErrNotFound("RetentionPolicy", policyId) + } + } + } + return err +} + +func (s *SqlRetentionPolicyStore) RemoveChannels(policyId string, channelIds []string) error { + if len(channelIds) == 0 { + return nil + } + builder := s.getQueryBuilder(). + Delete("RetentionPoliciesChannels"). + Where(sq.And{ + sq.Eq{"PolicyId": policyId}, + sq.Eq{"ChannelId": channelIds}, + }) + _, err := builder.RunWith(s.GetMaster()).Exec() + return err +} + +func (s *SqlRetentionPolicyStore) GetTeams(policyId string, offset, limit int) (teams []*model.Team, err error) { + const query = ` + SELECT Teams.* FROM RetentionPoliciesTeams + INNER JOIN Teams ON RetentionPoliciesTeams.TeamId = Teams.Id + WHERE RetentionPoliciesTeams.PolicyId = :PolicyId + ORDER BY Teams.DisplayName, Teams.Id + LIMIT :Limit + OFFSET :Offset` + props := map[string]interface{}{"PolicyId": policyId, "Limit": limit, "Offset": offset} + _, err = s.GetReplica().Select(&teams, query, props) + for _, team := range teams { + team.PolicyID = &policyId + } + return +} + +func (s *SqlRetentionPolicyStore) GetTeamsCount(policyId string) (int64, error) { + const query = ` + SELECT COUNT(*) + FROM RetentionPolicies + INNER JOIN RetentionPoliciesTeams ON RetentionPolicies.Id = RetentionPoliciesTeams.PolicyId + WHERE RetentionPolicies.Id = :PolicyId` + props := map[string]interface{}{"PolicyId": policyId} + return s.GetReplica().SelectInt(query, props) +} + +func (s *SqlRetentionPolicyStore) AddTeams(policyId string, teamIds []string) error { + if len(teamIds) == 0 { + return nil + } + if err := s.checkTeamsExist(teamIds); err != nil { + return err + } + builder := s.getQueryBuilder(). + Insert("RetentionPoliciesTeams"). + Columns("PolicyId", "TeamId") + for _, teamId := range teamIds { + builder = builder.Values(policyId, teamId) + } + _, err := builder.RunWith(s.GetMaster()).Exec() + return err +} + +func (s *SqlRetentionPolicyStore) RemoveTeams(policyId string, teamIds []string) error { + if len(teamIds) == 0 { + return nil + } + builder := s.getQueryBuilder(). + Delete("RetentionPoliciesTeams"). + Where(sq.And{ + sq.Eq{"PolicyId": policyId}, + sq.Eq{"TeamId": teamIds}, + }) + _, err := builder.RunWith(s.GetMaster()).Exec() + return err +} + +func (s *SqlRetentionPolicyStore) GetTeamPoliciesForUser(userID string, offset, limit int) (policies []*model.RetentionPolicyForTeam, err error) { + const query = ` + SELECT Teams.Id, RetentionPolicies.PostDuration + FROM Users + INNER JOIN TeamMembers ON Users.Id = TeamMembers.UserId + INNER JOIN Teams ON TeamMembers.TeamId = Teams.Id + INNER JOIN RetentionPoliciesTeams ON Teams.Id = RetentionPoliciesTeams.TeamId + INNER JOIN RetentionPolicies ON RetentionPoliciesTeams.PolicyId = RetentionPolicies.Id + WHERE Users.Id = :UserId + AND TeamMembers.DeleteAt = 0 + AND Teams.DeleteAt = 0 + ORDER BY Teams.Id + LIMIT :Limit + OFFSET :Offset` + props := map[string]interface{}{"UserId": userID, "Limit": limit, "Offset": offset} + _, err = s.GetReplica().Select(&policies, query, props) + return +} + +func (s *SqlRetentionPolicyStore) GetTeamPoliciesCountForUser(userID string) (int64, error) { + const query = ` + SELECT COUNT(*) + FROM Users + INNER JOIN TeamMembers ON Users.Id = TeamMembers.UserId + INNER JOIN Teams ON TeamMembers.TeamId = Teams.Id + INNER JOIN RetentionPoliciesTeams ON Teams.Id = RetentionPoliciesTeams.TeamId + INNER JOIN RetentionPolicies ON RetentionPoliciesTeams.PolicyId = RetentionPolicies.Id + WHERE Users.Id = :UserId + AND TeamMembers.DeleteAt = 0 + AND Teams.DeleteAt = 0` + props := map[string]interface{}{"UserId": userID} + return s.GetReplica().SelectInt(query, props) +} + +func (s *SqlRetentionPolicyStore) GetChannelPoliciesForUser(userID string, offset, limit int) (policies []*model.RetentionPolicyForChannel, err error) { + const query = ` + SELECT Channels.Id, RetentionPolicies.PostDuration + FROM Users + INNER JOIN ChannelMembers ON Users.Id = ChannelMembers.UserId + INNER JOIN Channels ON ChannelMembers.ChannelId = Channels.Id + INNER JOIN RetentionPoliciesChannels ON Channels.Id = RetentionPoliciesChannels.ChannelId + INNER JOIN RetentionPolicies ON RetentionPoliciesChannels.PolicyId = RetentionPolicies.Id + WHERE Users.Id = :UserId + AND Channels.DeleteAt = 0 + ORDER BY Channels.Id + LIMIT :Limit + OFFSET :Offset` + props := map[string]interface{}{"UserId": userID, "Limit": limit, "Offset": offset} + _, err = s.GetReplica().Select(&policies, query, props) + return +} + +func (s *SqlRetentionPolicyStore) GetChannelPoliciesCountForUser(userID string) (int64, error) { + const query = ` + SELECT COUNT(*) + FROM Users + INNER JOIN ChannelMembers ON Users.Id = ChannelMembers.UserId + INNER JOIN Channels ON ChannelMembers.ChannelId = Channels.Id + INNER JOIN RetentionPoliciesChannels ON Channels.Id = RetentionPoliciesChannels.ChannelId + INNER JOIN RetentionPolicies ON RetentionPoliciesChannels.PolicyId = RetentionPolicies.Id + WHERE Users.Id = :UserId + AND Channels.DeleteAt = 0` + props := map[string]interface{}{"UserId": userID} + return s.GetReplica().SelectInt(query, props) +} diff --git a/store/sqlstore/retention_policy_store_test.go b/store/sqlstore/retention_policy_store_test.go new file mode 100644 index 0000000000..c11cb89ca5 --- /dev/null +++ b/store/sqlstore/retention_policy_store_test.go @@ -0,0 +1,14 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +package sqlstore + +import ( + "testing" + + "github.com/mattermost/mattermost-server/v5/store/storetest" +) + +func TestRetentionPolicyStore(t *testing.T) { + StoreTestWithSqlStore(t, storetest.TestRetentionPolicyStore) +} diff --git a/store/sqlstore/store.go b/store/sqlstore/store.go index 2baa6e23eb..a78a47d89c 100644 --- a/store/sqlstore/store.go +++ b/store/sqlstore/store.go @@ -42,13 +42,17 @@ import ( type migrationDirection string const ( - IndexTypeFullText = "full_text" - IndexTypeFullTextFunc = "full_text_func" - IndexTypeDefault = "default" - PGDupTableErrorCode = "42P07" // see https://github.com/lib/pq/blob/master/error.go#L268 - MySQLDupTableErrorCode = uint16(1050) // see https://dev.mysql.com/doc/mysql-errors/5.7/en/server-error-reference.html#error_er_table_exists_error - DBPingAttempts = 18 - DBPingTimeoutSecs = 10 + IndexTypeFullText = "full_text" + IndexTypeFullTextFunc = "full_text_func" + IndexTypeDefault = "default" + PGDupTableErrorCode = "42P07" // see https://github.com/lib/pq/blob/master/error.go#L268 + MySQLDupTableErrorCode = uint16(1050) // see https://dev.mysql.com/doc/mysql-errors/5.7/en/server-error-reference.html#error_er_table_exists_error + PGForeignKeyViolationErrorCode = "23503" + MySQLForeignKeyViolationErrorCode = 1452 + PGDuplicateObjectErrorCode = "42710" + MySQLDuplicateObjectErrorCode = 1022 + DBPingAttempts = 18 + DBPingTimeoutSecs = 10 // This is a numerical version string by postgres. The format is // 2 characters for major, minor, and patch version prior to 10. // After 10, it's major and minor only. @@ -96,6 +100,7 @@ type SqlStoreStores struct { team store.TeamStore channel store.ChannelStore post store.PostStore + retentionPolicy store.RetentionPolicyStore thread store.ThreadStore user store.UserStore bot store.BotStore @@ -184,6 +189,7 @@ func New(settings model.SqlSettings, metrics einterfaces.MetricsInterface) *SqlS store.stores.team = newSqlTeamStore(store) store.stores.channel = newSqlChannelStore(store, metrics) store.stores.post = newSqlPostStore(store, metrics) + store.stores.retentionPolicy = newSqlRetentionPolicyStore(store, metrics) store.stores.user = newSqlUserStore(store, metrics) store.stores.bot = newSqlBotStore(store, metrics) store.stores.audit = newSqlAuditStore(store) @@ -237,6 +243,7 @@ func New(settings model.SqlSettings, metrics einterfaces.MetricsInterface) *SqlS store.stores.channel.(*SqlChannelStore).createIndexesIfNotExists() store.stores.post.(*SqlPostStore).createIndexesIfNotExists() + store.stores.retentionPolicy.(*SqlRetentionPolicyStore).createIndexesIfNotExists() store.stores.thread.(*SqlThreadStore).createIndexesIfNotExists() store.stores.user.(*SqlUserStore).createIndexesIfNotExists() store.stores.bot.(*SqlBotStore).createIndexesIfNotExists() @@ -1077,6 +1084,30 @@ func (ss *SqlStore) createIndexIfNotExists(indexName string, tableName string, c return true } +func (ss *SqlStore) CreateForeignKeyIfNotExists( + tableName, columnName, refTableName, refColumnName string, + onDeleteCascade bool, +) (err error) { + deleteClause := "" + if onDeleteCascade { + deleteClause = "ON DELETE CASCADE" + } + constraintName := "FK_" + tableName + "_" + refTableName + sQuery := ` + ALTER TABLE ` + tableName + ` + ADD CONSTRAINT ` + constraintName + ` + FOREIGN KEY (` + columnName + `) REFERENCES ` + refTableName + ` (` + refColumnName + `) + ` + deleteClause + `;` + _, err = ss.GetMaster().ExecNoTimeout(sQuery) + if IsConstraintAlreadyExistsError(err) { + err = nil + } + if err != nil { + mlog.Warn("Could not create foreign key: " + err.Error()) + } + return +} + func (ss *SqlStore) RemoveIndexIfExists(indexName string, tableName string) bool { if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES { @@ -1122,6 +1153,20 @@ func (ss *SqlStore) RemoveIndexIfExists(indexName string, tableName string) bool return true } +func IsConstraintAlreadyExistsError(err error) bool { + switch dbErr := err.(type) { + case *pq.Error: + if dbErr.Code == PGDuplicateObjectErrorCode { + return true + } + case *mysql.MySQLError: + if dbErr.Number == MySQLDuplicateObjectErrorCode { + return true + } + } + return false +} + func IsUniqueConstraintError(err error, indexName []string) bool { unique := false if pqErr, ok := err.(*pq.Error); ok && pqErr.Code == "23505" { @@ -1198,6 +1243,10 @@ func (ss *SqlStore) Post() store.PostStore { return ss.stores.post } +func (ss *SqlStore) RetentionPolicy() store.RetentionPolicyStore { + return ss.stores.retentionPolicy +} + func (ss *SqlStore) User() store.UserStore { return ss.stores.user } diff --git a/store/sqlstore/team_store.go b/store/sqlstore/team_store.go index bfd9f74a95..72b277bcbe 100644 --- a/store/sqlstore/team_store.go +++ b/store/sqlstore/team_store.go @@ -370,12 +370,15 @@ func (s SqlTeamStore) GetByNames(names []string) ([]*model.Team, error) { return teams, nil } -func (s SqlTeamStore) teamSearchQuery(term string, opts *model.TeamSearch, countQuery bool) sq.SelectBuilder { +func (s SqlTeamStore) teamSearchQuery(opts *model.TeamSearch, countQuery bool) sq.SelectBuilder { var selectStr string if countQuery { selectStr = "count(*)" } else { - selectStr = "*" + selectStr = "t.*" + if opts.IncludePolicyID != nil && *opts.IncludePolicyID { + selectStr += ", RetentionPoliciesTeams.PolicyId" + } } query := s.getQueryBuilder(). @@ -391,6 +394,7 @@ func (s SqlTeamStore) teamSearchQuery(term string, opts *model.TeamSearch, count } } + term := opts.Term if term != "" { term = sanitizeSearchTerm(term, "\\") term = wildcardSearchTerm(term) @@ -403,6 +407,19 @@ func (s SqlTeamStore) teamSearchQuery(term string, opts *model.TeamSearch, count query = query.Where(fmt.Sprintf("(Name %[1]s ? OR DisplayName %[1]s ?)", operatorKeyword), term, term) } + if opts.PolicyID != nil && *opts.PolicyID != "" { + query = query. + InnerJoin("RetentionPoliciesTeams ON t.Id = RetentionPoliciesTeams.TeamId"). + Where(sq.Eq{"RetentionPoliciesTeams.PolicyId": *opts.PolicyID}) + } else if opts.ExcludePolicyConstrained != nil && *opts.ExcludePolicyConstrained { + query = query. + LeftJoin("RetentionPoliciesTeams ON t.Id = RetentionPoliciesTeams.TeamId"). + Where("RetentionPoliciesTeams.TeamId IS NULL") + } else if opts.IncludePolicyID != nil && *opts.IncludePolicyID { + query = query. + LeftJoin("RetentionPoliciesTeams ON t.Id = RetentionPoliciesTeams.TeamId") + } + var teamFilters sq.Sqlizer var openInviteFilter sq.Sqlizer if opts.AllowOpenInvite != nil { @@ -442,6 +459,11 @@ func (s SqlTeamStore) teamSearchQuery(term string, opts *model.TeamSearch, count } } + if opts.TeamType != nil { + teamTypeFilter := sq.Eq{"Type": *opts.TeamType} + teamFilters = sq.And{teamFilters, teamTypeFilter} + } + query = query.Where(teamFilters) return query @@ -449,41 +471,41 @@ func (s SqlTeamStore) teamSearchQuery(term string, opts *model.TeamSearch, count // SearchAll returns from the database a list of teams that match the Name or DisplayName // passed as the term search parameter. -func (s SqlTeamStore) SearchAll(term string, opts *model.TeamSearch) ([]*model.Team, error) { +func (s SqlTeamStore) SearchAll(opts *model.TeamSearch) ([]*model.Team, error) { var teams []*model.Team - queryString, args, err := s.teamSearchQuery(term, opts, false).ToSql() + queryString, args, err := s.teamSearchQuery(opts, false).ToSql() if err != nil { return nil, errors.Wrap(err, "team_tosql") } if _, err = s.GetReplica().Select(&teams, queryString, args...); err != nil { - return nil, errors.Wrapf(err, "failed to find Teams with term=%s", term) + return nil, errors.Wrapf(err, "failed to find Teams with term=%s", opts.Term) } return teams, nil } // SearchAllPaged returns a teams list and the total count of teams that matched the search. -func (s SqlTeamStore) SearchAllPaged(term string, opts *model.TeamSearch) ([]*model.Team, int64, error) { +func (s SqlTeamStore) SearchAllPaged(opts *model.TeamSearch) ([]*model.Team, int64, error) { var teams []*model.Team var totalCount int64 - queryString, args, err := s.teamSearchQuery(term, opts, false).ToSql() + queryString, args, err := s.teamSearchQuery(opts, false).ToSql() if err != nil { return nil, 0, errors.Wrap(err, "team_tosql") } if _, err = s.GetReplica().Select(&teams, queryString, args...); err != nil { - return nil, 0, errors.Wrapf(err, "failed to find Teams with term=%s", term) + return nil, 0, errors.Wrapf(err, "failed to find Teams with term=%s", opts.Term) } - queryString, args, err = s.teamSearchQuery(term, opts, true).ToSql() + queryString, args, err = s.teamSearchQuery(opts, true).ToSql() if err != nil { return nil, 0, errors.Wrap(err, "team_tosql") } totalCount, err = s.GetReplica().SelectInt(queryString, args...) if err != nil { - return nil, 0, errors.Wrapf(err, "failed to count Teams with term=%s", term) + return nil, 0, errors.Wrapf(err, "failed to count Teams with term=%s", opts.Term) } return teams, totalCount, nil @@ -491,53 +513,18 @@ func (s SqlTeamStore) SearchAllPaged(term string, opts *model.TeamSearch) ([]*mo // SearchOpen returns from the database a list of public teams that match the Name or DisplayName // passed as the term search parameter. -func (s SqlTeamStore) SearchOpen(term string) ([]*model.Team, error) { - var teams []*model.Team - - term = sanitizeSearchTerm(term, "\\") - term = wildcardSearchTerm(term) - query := s.teamsQuery.Where(sq.Eq{"Type": "O", "AllowOpenInvite": true}) - if s.DriverName() == model.DATABASE_DRIVER_MYSQL { - query = query.Where(sq.Or{sq.Like{"Name": term}, sq.Like{"DisplayName": term}}) - } else { - query = query.Where(sq.Or{sq.ILike{"Name": term}, sq.ILike{"DisplayName": term}}) - } - - queryString, args, err := query.ToSql() - if err != nil { - return nil, errors.Wrap(err, "team_tosql") - } - - if _, err = s.GetReplica().Select(&teams, queryString, args...); err != nil { - return nil, errors.Wrapf(err, "failed to count Teams with term=%s", term) - } - - return teams, nil +func (s SqlTeamStore) SearchOpen(opts *model.TeamSearch) ([]*model.Team, error) { + opts.TeamType = model.NewString("O") + opts.AllowOpenInvite = model.NewBool(true) + return s.SearchAll(opts) } // SearchPrivate returns from the database a list of private teams that match the Name or DisplayName // passed as the term search parameter. -func (s SqlTeamStore) SearchPrivate(term string) ([]*model.Team, error) { - var teams []*model.Team - - term = sanitizeSearchTerm(term, "\\") - term = wildcardSearchTerm(term) - query := s.teamsQuery.Where(sq.Eq{"Type": "O", "AllowOpenInvite": false}) - if s.DriverName() == model.DATABASE_DRIVER_MYSQL { - query = query.Where(sq.Or{sq.Like{"Name": term}, sq.Like{"DisplayName": term}}) - } else { - query = query.Where(sq.Or{sq.ILike{"Name": term}, sq.ILike{"DisplayName": term}}) - } - - queryString, args, err := query.ToSql() - if err != nil { - return nil, errors.Wrap(err, "team_tosql") - } - - if _, err = s.GetReplica().Select(&teams, queryString, args...); err != nil { - return nil, errors.Wrapf(err, "failed to count Teams with term=%s", term) - } - return teams, nil +func (s SqlTeamStore) SearchPrivate(opts *model.TeamSearch) ([]*model.Team, error) { + opts.TeamType = model.NewString("O") + opts.AllowOpenInvite = model.NewBool(false) + return s.SearchAll(opts) } // GetAll returns all teams @@ -558,13 +545,35 @@ func (s SqlTeamStore) GetAll() ([]*model.Team, error) { } // GetAllPage returns teams, up to a total limit passed as parameter and paginated by offset number passed as parameter. -func (s SqlTeamStore) GetAllPage(offset int, limit int) ([]*model.Team, error) { +func (s SqlTeamStore) GetAllPage(offset int, limit int, opts *model.TeamSearch) ([]*model.Team, error) { var teams []*model.Team - query, args, err := s.teamsQuery. + selectString := "Teams.*" + if opts != nil && opts.IncludePolicyID != nil && *opts.IncludePolicyID { + selectString += ", RetentionPoliciesTeams.PolicyId" + } + + builder := s.getQueryBuilder(). + Select(selectString). + From("Teams"). OrderBy("DisplayName"). Limit(uint64(limit)). - Offset(uint64(offset)).ToSql() + Offset(uint64(offset)) + + if opts != nil { + if (opts.ExcludePolicyConstrained != nil && *opts.ExcludePolicyConstrained) || + (opts.IncludePolicyID != nil && *opts.IncludePolicyID) { + builder = builder.LeftJoin("RetentionPoliciesTeams ON Teams.Id = RetentionPoliciesTeams.TeamId") + } + if opts.ExcludePolicyConstrained != nil && *opts.ExcludePolicyConstrained { + builder = builder.Where("RetentionPoliciesTeams.TeamId IS NULL") + } + if opts.AllowOpenInvite != nil { + builder = builder.Where(sq.Eq{"AllowOpenInvite": *opts.AllowOpenInvite}) + } + } + + query, args, err := builder.ToSql() if err != nil { return nil, errors.Wrap(err, "team_tosql") @@ -608,43 +617,6 @@ func (s SqlTeamStore) GetAllPrivateTeamListing() ([]*model.Team, error) { return data, nil } -// GetAllPublicTeamPageListing returns public teams, up to a total limit passed as parameter and paginated by offset number passed as parameter. -func (s SqlTeamStore) GetAllPublicTeamPageListing(offset int, limit int) ([]*model.Team, error) { - query, args, err := s.teamsQuery.Where(sq.Eq{"AllowOpenInvite": true}). - OrderBy("DisplayName"). - Limit(uint64(limit)). - Offset(uint64(offset)).ToSql() - if err != nil { - return nil, errors.Wrap(err, "team_tosql") - } - - var data []*model.Team - if _, err = s.GetReplica().Select(&data, query, args...); err != nil { - return nil, errors.Wrap(err, "failed to find Teams") - } - - return data, nil -} - -// GetAllPrivateTeamPageListing returns private teams, up to a total limit passed as paramater and paginated by offset number passed as parameter. -func (s SqlTeamStore) GetAllPrivateTeamPageListing(offset int, limit int) ([]*model.Team, error) { - query, args, err := s.teamsQuery.Where(sq.Eq{"AllowOpenInvite": false}). - OrderBy("DisplayName"). - Limit(uint64(limit)). - Offset(uint64(offset)).ToSql() - - if err != nil { - return nil, errors.Wrap(err, "team_tosql") - } - - var data []*model.Team - if _, err = s.GetReplica().Select(&data, query, args...); err != nil { - return nil, errors.Wrap(err, "failed to find Teams") - } - - return data, nil -} - // GetAllTeamListing returns all public teams. func (s SqlTeamStore) GetAllTeamListing() ([]*model.Team, error) { query, args, err := s.teamsQuery.Where(sq.Eq{"AllowOpenInvite": true}). @@ -662,25 +634,6 @@ func (s SqlTeamStore) GetAllTeamListing() ([]*model.Team, error) { return data, nil } -// GetAllTeamPageListing returns public teams, up to a total limit passed as parameter and paginated by offset number passed as parameter. -func (s SqlTeamStore) GetAllTeamPageListing(offset int, limit int) ([]*model.Team, error) { - query, args, err := s.teamsQuery.Where(sq.Eq{"AllowOpenInvite": true}). - OrderBy("DisplayName"). - Limit(uint64(limit)). - Offset(uint64(offset)).ToSql() - - if err != nil { - return nil, errors.Wrap(err, "team_tosql") - } - - var teams []*model.Team - if _, err = s.GetReplica().Select(&teams, query, args...); err != nil { - return nil, errors.Wrap(err, "failed to find Teams") - } - - return teams, nil -} - // PermanentDelete permanently deletes from the database the team entry that matches the teamId passed as parameter. // To soft-delete the team you can Update it with the DeleteAt field set to the current millisecond using model.GetMillis() func (s SqlTeamStore) PermanentDelete(teamId string) error { @@ -696,49 +649,15 @@ func (s SqlTeamStore) PermanentDelete(teamId string) error { return nil } -// AnalyticsPublicTeamCount returns the number of active public teams. -func (s SqlTeamStore) AnalyticsPublicTeamCount() (int64, error) { - query, args, err := s.getQueryBuilder(). - Select("COUNT(*) FROM Teams"). - Where(sq.Eq{"DeleteAt": 0, "AllowOpenInvite": true}).ToSql() - - if err != nil { - return 0, errors.Wrap(err, "team_tosql") - } - - c, err := s.GetReplica().SelectInt(query, args...) - - if err != nil { - return int64(0), errors.Wrap(err, "failed to count Teams") - } - - return c, nil -} - -// AnalyticsPrivateTeamCount returns the number of active private teams. -func (s SqlTeamStore) AnalyticsPrivateTeamCount() (int64, error) { - query, args, err := s.getQueryBuilder(). - Select("COUNT(*) FROM Teams"). - Where(sq.Eq{"DeleteAt": 0, "AllowOpenInvite": false}).ToSql() - - if err != nil { - return 0, errors.Wrap(err, "team_tosql") - } - c, err := s.GetReplica().SelectInt(query, args...) - - if err != nil { - return int64(0), errors.Wrap(err, "failed to count Teams") - } - - return c, nil -} - -// AnalyticsTeamCount returns the total number of teams including deleted teams if parameter passed is set to 'true'. -func (s SqlTeamStore) AnalyticsTeamCount(includeDeleted bool) (int64, error) { +// AnalyticsTeamCount returns the total number of teams. +func (s SqlTeamStore) AnalyticsTeamCount(opts *model.TeamSearch) (int64, error) { query := s.getQueryBuilder().Select("COUNT(*) FROM Teams") - if !includeDeleted { + if opts == nil || (opts.IncludeDeleted != nil && !*opts.IncludeDeleted) { query = query.Where(sq.Eq{"DeleteAt": 0}) } + if opts != nil && opts.AllowOpenInvite != nil { + query = query.Where(sq.Eq{"AllowOpenInvite": *opts.AllowOpenInvite}) + } queryString, args, err := query.ToSql() if err != nil { diff --git a/store/store.go b/store/store.go index 38c69e23d1..47ac288053 100644 --- a/store/store.go +++ b/store/store.go @@ -23,6 +23,7 @@ type Store interface { Team() TeamStore Channel() ChannelStore Post() PostStore + RetentionPolicy() RetentionPolicyStore Thread() ThreadStore User() UserStore Bot() BotStore @@ -74,29 +75,45 @@ type Store interface { Context() context.Context } +type RetentionPolicyStore interface { + Save(policy *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, error) + Patch(patch *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, error) + Get(id string) (*model.RetentionPolicyWithTeamAndChannelCounts, error) + GetAll(offset, limit int) ([]*model.RetentionPolicyWithTeamAndChannelCounts, error) + GetCount() (int64, error) + Delete(id string) error + GetChannels(policyId string, offset, limit int) (model.ChannelListWithTeamData, error) + GetChannelsCount(policyId string) (int64, error) + AddChannels(policyId string, channelIds []string) error + RemoveChannels(policyId string, channelIds []string) error + GetTeams(policyId string, offset, limit int) ([]*model.Team, error) + GetTeamsCount(policyId string) (int64, error) + AddTeams(policyId string, teamIds []string) error + RemoveTeams(policyId string, teamIds []string) error + GetTeamPoliciesForUser(userID string, offset, limit int) ([]*model.RetentionPolicyForTeam, error) + GetTeamPoliciesCountForUser(userID string) (int64, error) + GetChannelPoliciesForUser(userID string, offset, limit int) ([]*model.RetentionPolicyForChannel, error) + GetChannelPoliciesCountForUser(userID string) (int64, error) +} + type TeamStore interface { Save(team *model.Team) (*model.Team, error) Update(team *model.Team) (*model.Team, error) Get(id string) (*model.Team, error) GetByName(name string) (*model.Team, error) GetByNames(name []string) ([]*model.Team, error) - SearchAll(term string, opts *model.TeamSearch) ([]*model.Team, error) - SearchAllPaged(term string, opts *model.TeamSearch) ([]*model.Team, int64, error) - SearchOpen(term string) ([]*model.Team, error) - SearchPrivate(term string) ([]*model.Team, error) + SearchAll(opts *model.TeamSearch) ([]*model.Team, error) + SearchAllPaged(opts *model.TeamSearch) ([]*model.Team, int64, error) + SearchOpen(opts *model.TeamSearch) ([]*model.Team, error) + SearchPrivate(opts *model.TeamSearch) ([]*model.Team, error) GetAll() ([]*model.Team, error) - GetAllPage(offset int, limit int) ([]*model.Team, error) + GetAllPage(offset int, limit int, opts *model.TeamSearch) ([]*model.Team, error) GetAllPrivateTeamListing() ([]*model.Team, error) - GetAllPrivateTeamPageListing(offset int, limit int) ([]*model.Team, error) - GetAllPublicTeamPageListing(offset int, limit int) ([]*model.Team, error) GetAllTeamListing() ([]*model.Team, error) - GetAllTeamPageListing(offset int, limit int) ([]*model.Team, error) GetTeamsByUserId(userID string) ([]*model.Team, error) GetByInviteId(inviteID string) (*model.Team, error) PermanentDelete(teamID string) error - AnalyticsTeamCount(includeDeleted bool) (int64, error) - AnalyticsPublicTeamCount() (int64, error) - AnalyticsPrivateTeamCount() (int64, error) + AnalyticsTeamCount(opts *model.TeamSearch) (int64, error) SaveMultipleMembers(members []*model.TeamMember, maxUsersPerTeam int) ([]*model.TeamMember, error) SaveMember(member *model.TeamMember, maxUsersPerTeam int) (*model.TeamMember, error) UpdateMember(member *model.TeamMember) (*model.TeamMember, error) @@ -847,17 +864,23 @@ type SharedChannelStore interface { // PerPage number of results per page, if paginated. // type ChannelSearchOpts struct { - NotAssociatedToGroup string - IncludeDeleted bool - Deleted bool - ExcludeChannelNames []string - TeamIds []string - GroupConstrained bool - ExcludeGroupConstrained bool - Public bool - Private bool - Page *int - PerPage *int + Term string + NotAssociatedToGroup string + IncludeDeleted bool + Deleted bool + ExcludeChannelNames []string + TeamIds []string + GroupConstrained bool + ExcludeGroupConstrained bool + PolicyID string + ExcludePolicyConstrained bool + IncludePolicyID bool + IncludeTeamInfo bool + CountOnly bool + Public bool + Private bool + Page *int + PerPage *int } func (c *ChannelSearchOpts) IsPaginated() bool { diff --git a/store/storetest/channel_store.go b/store/storetest/channel_store.go index 00f78811c7..e97fe9fdb1 100644 --- a/store/storetest/channel_store.go +++ b/store/storetest/channel_store.go @@ -3375,6 +3375,33 @@ func testChannelStoreGetAllChannels(t *testing.T, ss store.Store, s SqlStore) { require.NoError(t, nErr) assert.Len(t, *list, 1) + // Exclude policy constrained + policy, nErr := ss.RetentionPolicy().Save(&model.RetentionPolicyWithTeamAndChannelIDs{ + RetentionPolicy: model.RetentionPolicy{ + DisplayName: "Policy 1", + PostDuration: model.NewInt64(30), + }, + ChannelIDs: []string{c1.Id}, + }) + require.NoError(t, nErr) + list, nErr = ss.Channel().GetAllChannels(0, 10, store.ChannelSearchOpts{ExcludePolicyConstrained: true}) + require.NoError(t, nErr) + assert.Len(t, *list, 1) + assert.Equal(t, c3.Id, (*list)[0].Id) + + // Without the policy ID + list, nErr = ss.Channel().GetAllChannels(0, 1, store.ChannelSearchOpts{}) + require.NoError(t, nErr) + assert.Len(t, *list, 1) + assert.Equal(t, c1.Id, (*list)[0].Id) + assert.Nil(t, (*list)[0].PolicyID) + // With the policy ID + list, nErr = ss.Channel().GetAllChannels(0, 1, store.ChannelSearchOpts{IncludePolicyID: true}) + require.NoError(t, nErr) + assert.Len(t, *list, 1) + assert.Equal(t, c1.Id, (*list)[0].Id) + assert.Equal(t, *(*list)[0].PolicyID, policy.ID) + // Manually truncate Channels table until testlib can handle cleanups s.GetMaster().Exec("TRUNCATE Channels") } @@ -5459,6 +5486,16 @@ func testChannelStoreSearchAllChannels(t *testing.T, ss store.Store) { } _, nErr = ss.Channel().Save(&o14, -1) require.NoError(t, nErr) + + _, nErr = ss.RetentionPolicy().Save(&model.RetentionPolicyWithTeamAndChannelIDs{ + RetentionPolicy: model.RetentionPolicy{ + DisplayName: "Policy 1", + PostDuration: model.NewInt64(30), + }, + ChannelIDs: []string{o14.Id}, + }) + require.NoError(t, nErr) + testCases := []struct { Description string Term string @@ -5494,6 +5531,7 @@ func testChannelStoreSearchAllChannels(t *testing.T, ss store.Store) { {"Filter group constrained", "", store.ChannelSearchOpts{IncludeDeleted: false, GroupConstrained: true, Page: model.NewInt(0), PerPage: model.NewInt(5)}, &model.ChannelList{&o5}, 1}, {"Filter exclude group constrained and include deleted", "", store.ChannelSearchOpts{IncludeDeleted: true, ExcludeGroupConstrained: true, Page: model.NewInt(0), PerPage: model.NewInt(5)}, &model.ChannelList{&o1, &o2, &o3, &o4, &o6}, 13}, {"Filter private and exclude group constrained", "", store.ChannelSearchOpts{IncludeDeleted: false, ExcludeGroupConstrained: true, Private: true, Page: model.NewInt(0), PerPage: model.NewInt(5)}, &model.ChannelList{&o4, &o8}, 2}, + {"Exclude policy constrained", "", store.ChannelSearchOpts{ExcludePolicyConstrained: true}, &model.ChannelList{&o1, &o2, &o3, &o4, &o5, &o6, &o7, &o8, &o9, &o10, &o11, &o12}, 0}, {"Filter team 2", "", store.ChannelSearchOpts{IncludeDeleted: false, TeamIds: []string{t2.Id}, Page: model.NewInt(0), PerPage: model.NewInt(5)}, &model.ChannelList{&o2, &o14}, 2}, {"Filter team 2, private", "", store.ChannelSearchOpts{IncludeDeleted: false, TeamIds: []string{t2.Id}, Private: true, Page: model.NewInt(0), PerPage: model.NewInt(5)}, &model.ChannelList{}, 0}, {"Filter team 1 and team 2, private", "", store.ChannelSearchOpts{IncludeDeleted: false, TeamIds: []string{t1.Id, t2.Id}, Private: true, Page: model.NewInt(0), PerPage: model.NewInt(5)}, &model.ChannelList{&o4, &o5, &o8}, 3}, diff --git a/store/storetest/mocks/RetentionPolicyStore.go b/store/storetest/mocks/RetentionPolicyStore.go new file mode 100644 index 0000000000..4b3dac2818 --- /dev/null +++ b/store/storetest/mocks/RetentionPolicyStore.go @@ -0,0 +1,374 @@ +// Code generated by mockery v1.0.0. DO NOT EDIT. + +// Regenerate this file using `make store-mocks`. + +package mocks + +import ( + model "github.com/mattermost/mattermost-server/v5/model" + mock "github.com/stretchr/testify/mock" +) + +// RetentionPolicyStore is an autogenerated mock type for the RetentionPolicyStore type +type RetentionPolicyStore struct { + mock.Mock +} + +// AddChannels provides a mock function with given fields: policyId, channelIds +func (_m *RetentionPolicyStore) AddChannels(policyId string, channelIds []string) error { + ret := _m.Called(policyId, channelIds) + + var r0 error + if rf, ok := ret.Get(0).(func(string, []string) error); ok { + r0 = rf(policyId, channelIds) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// AddTeams provides a mock function with given fields: policyId, teamIds +func (_m *RetentionPolicyStore) AddTeams(policyId string, teamIds []string) error { + ret := _m.Called(policyId, teamIds) + + var r0 error + if rf, ok := ret.Get(0).(func(string, []string) error); ok { + r0 = rf(policyId, teamIds) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Delete provides a mock function with given fields: id +func (_m *RetentionPolicyStore) Delete(id string) error { + ret := _m.Called(id) + + var r0 error + if rf, ok := ret.Get(0).(func(string) error); ok { + r0 = rf(id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Get provides a mock function with given fields: id +func (_m *RetentionPolicyStore) Get(id string) (*model.RetentionPolicyWithTeamAndChannelCounts, error) { + ret := _m.Called(id) + + var r0 *model.RetentionPolicyWithTeamAndChannelCounts + if rf, ok := ret.Get(0).(func(string) *model.RetentionPolicyWithTeamAndChannelCounts); ok { + r0 = rf(id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.RetentionPolicyWithTeamAndChannelCounts) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetAll provides a mock function with given fields: offset, limit +func (_m *RetentionPolicyStore) GetAll(offset int, limit int) ([]*model.RetentionPolicyWithTeamAndChannelCounts, error) { + ret := _m.Called(offset, limit) + + var r0 []*model.RetentionPolicyWithTeamAndChannelCounts + if rf, ok := ret.Get(0).(func(int, int) []*model.RetentionPolicyWithTeamAndChannelCounts); ok { + r0 = rf(offset, limit) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*model.RetentionPolicyWithTeamAndChannelCounts) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(int, int) error); ok { + r1 = rf(offset, limit) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetChannelPoliciesCountForUser provides a mock function with given fields: userID +func (_m *RetentionPolicyStore) GetChannelPoliciesCountForUser(userID string) (int64, error) { + ret := _m.Called(userID) + + var r0 int64 + if rf, ok := ret.Get(0).(func(string) int64); ok { + r0 = rf(userID) + } else { + r0 = ret.Get(0).(int64) + } + + var r1 error + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(userID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetChannelPoliciesForUser provides a mock function with given fields: userID, offset, limit +func (_m *RetentionPolicyStore) GetChannelPoliciesForUser(userID string, offset int, limit int) ([]*model.RetentionPolicyForChannel, error) { + ret := _m.Called(userID, offset, limit) + + var r0 []*model.RetentionPolicyForChannel + if rf, ok := ret.Get(0).(func(string, int, int) []*model.RetentionPolicyForChannel); ok { + r0 = rf(userID, offset, limit) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*model.RetentionPolicyForChannel) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string, int, int) error); ok { + r1 = rf(userID, offset, limit) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetChannels provides a mock function with given fields: policyId, offset, limit +func (_m *RetentionPolicyStore) GetChannels(policyId string, offset int, limit int) (model.ChannelListWithTeamData, error) { + ret := _m.Called(policyId, offset, limit) + + var r0 model.ChannelListWithTeamData + if rf, ok := ret.Get(0).(func(string, int, int) model.ChannelListWithTeamData); ok { + r0 = rf(policyId, offset, limit) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(model.ChannelListWithTeamData) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string, int, int) error); ok { + r1 = rf(policyId, offset, limit) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetChannelsCount provides a mock function with given fields: policyId +func (_m *RetentionPolicyStore) GetChannelsCount(policyId string) (int64, error) { + ret := _m.Called(policyId) + + var r0 int64 + if rf, ok := ret.Get(0).(func(string) int64); ok { + r0 = rf(policyId) + } else { + r0 = ret.Get(0).(int64) + } + + var r1 error + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(policyId) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetCount provides a mock function with given fields: +func (_m *RetentionPolicyStore) GetCount() (int64, error) { + ret := _m.Called() + + var r0 int64 + if rf, ok := ret.Get(0).(func() int64); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(int64) + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetTeamPoliciesCountForUser provides a mock function with given fields: userID +func (_m *RetentionPolicyStore) GetTeamPoliciesCountForUser(userID string) (int64, error) { + ret := _m.Called(userID) + + var r0 int64 + if rf, ok := ret.Get(0).(func(string) int64); ok { + r0 = rf(userID) + } else { + r0 = ret.Get(0).(int64) + } + + var r1 error + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(userID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetTeamPoliciesForUser provides a mock function with given fields: userID, offset, limit +func (_m *RetentionPolicyStore) GetTeamPoliciesForUser(userID string, offset int, limit int) ([]*model.RetentionPolicyForTeam, error) { + ret := _m.Called(userID, offset, limit) + + var r0 []*model.RetentionPolicyForTeam + if rf, ok := ret.Get(0).(func(string, int, int) []*model.RetentionPolicyForTeam); ok { + r0 = rf(userID, offset, limit) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*model.RetentionPolicyForTeam) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string, int, int) error); ok { + r1 = rf(userID, offset, limit) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetTeams provides a mock function with given fields: policyId, offset, limit +func (_m *RetentionPolicyStore) GetTeams(policyId string, offset int, limit int) ([]*model.Team, error) { + ret := _m.Called(policyId, offset, limit) + + var r0 []*model.Team + if rf, ok := ret.Get(0).(func(string, int, int) []*model.Team); ok { + r0 = rf(policyId, offset, limit) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*model.Team) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string, int, int) error); ok { + r1 = rf(policyId, offset, limit) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetTeamsCount provides a mock function with given fields: policyId +func (_m *RetentionPolicyStore) GetTeamsCount(policyId string) (int64, error) { + ret := _m.Called(policyId) + + var r0 int64 + if rf, ok := ret.Get(0).(func(string) int64); ok { + r0 = rf(policyId) + } else { + r0 = ret.Get(0).(int64) + } + + var r1 error + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(policyId) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Patch provides a mock function with given fields: patch +func (_m *RetentionPolicyStore) Patch(patch *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, error) { + ret := _m.Called(patch) + + var r0 *model.RetentionPolicyWithTeamAndChannelCounts + if rf, ok := ret.Get(0).(func(*model.RetentionPolicyWithTeamAndChannelIDs) *model.RetentionPolicyWithTeamAndChannelCounts); ok { + r0 = rf(patch) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.RetentionPolicyWithTeamAndChannelCounts) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(*model.RetentionPolicyWithTeamAndChannelIDs) error); ok { + r1 = rf(patch) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RemoveChannels provides a mock function with given fields: policyId, channelIds +func (_m *RetentionPolicyStore) RemoveChannels(policyId string, channelIds []string) error { + ret := _m.Called(policyId, channelIds) + + var r0 error + if rf, ok := ret.Get(0).(func(string, []string) error); ok { + r0 = rf(policyId, channelIds) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RemoveTeams provides a mock function with given fields: policyId, teamIds +func (_m *RetentionPolicyStore) RemoveTeams(policyId string, teamIds []string) error { + ret := _m.Called(policyId, teamIds) + + var r0 error + if rf, ok := ret.Get(0).(func(string, []string) error); ok { + r0 = rf(policyId, teamIds) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Save provides a mock function with given fields: policy +func (_m *RetentionPolicyStore) Save(policy *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, error) { + ret := _m.Called(policy) + + var r0 *model.RetentionPolicyWithTeamAndChannelCounts + if rf, ok := ret.Get(0).(func(*model.RetentionPolicyWithTeamAndChannelIDs) *model.RetentionPolicyWithTeamAndChannelCounts); ok { + r0 = rf(policy) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.RetentionPolicyWithTeamAndChannelCounts) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(*model.RetentionPolicyWithTeamAndChannelIDs) error); ok { + r1 = rf(policy) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/store/storetest/mocks/Store.go b/store/storetest/mocks/Store.go index e38db2b1d5..7dc2937831 100644 --- a/store/storetest/mocks/Store.go +++ b/store/storetest/mocks/Store.go @@ -476,6 +476,22 @@ func (_m *Store) ReplicaLagTime() error { return r0 } +// RetentionPolicy provides a mock function with given fields: +func (_m *Store) RetentionPolicy() store.RetentionPolicyStore { + ret := _m.Called() + + var r0 store.RetentionPolicyStore + if rf, ok := ret.Get(0).(func() store.RetentionPolicyStore); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(store.RetentionPolicyStore) + } + } + + return r0 +} + // Role provides a mock function with given fields: func (_m *Store) Role() store.RoleStore { ret := _m.Called() diff --git a/store/storetest/mocks/TeamStore.go b/store/storetest/mocks/TeamStore.go index b16571a575..ef8160b8c0 100644 --- a/store/storetest/mocks/TeamStore.go +++ b/store/storetest/mocks/TeamStore.go @@ -37,62 +37,20 @@ func (_m *TeamStore) AnalyticsGetTeamCountForScheme(schemeID string) (int64, err return r0, r1 } -// AnalyticsPrivateTeamCount provides a mock function with given fields: -func (_m *TeamStore) AnalyticsPrivateTeamCount() (int64, error) { - ret := _m.Called() +// AnalyticsTeamCount provides a mock function with given fields: opts +func (_m *TeamStore) AnalyticsTeamCount(opts *model.TeamSearch) (int64, error) { + ret := _m.Called(opts) var r0 int64 - if rf, ok := ret.Get(0).(func() int64); ok { - r0 = rf() + if rf, ok := ret.Get(0).(func(*model.TeamSearch) int64); ok { + r0 = rf(opts) } else { r0 = ret.Get(0).(int64) } var r1 error - if rf, ok := ret.Get(1).(func() error); ok { - r1 = rf() - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// AnalyticsPublicTeamCount provides a mock function with given fields: -func (_m *TeamStore) AnalyticsPublicTeamCount() (int64, error) { - ret := _m.Called() - - var r0 int64 - if rf, ok := ret.Get(0).(func() int64); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(int64) - } - - var r1 error - if rf, ok := ret.Get(1).(func() error); ok { - r1 = rf() - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// AnalyticsTeamCount provides a mock function with given fields: includeDeleted -func (_m *TeamStore) AnalyticsTeamCount(includeDeleted bool) (int64, error) { - ret := _m.Called(includeDeleted) - - var r0 int64 - if rf, ok := ret.Get(0).(func(bool) int64); ok { - r0 = rf(includeDeleted) - } else { - r0 = ret.Get(0).(int64) - } - - var r1 error - if rf, ok := ret.Get(1).(func(bool) error); ok { - r1 = rf(includeDeleted) + if rf, ok := ret.Get(1).(func(*model.TeamSearch) error); ok { + r1 = rf(opts) } else { r1 = ret.Error(1) } @@ -209,13 +167,13 @@ func (_m *TeamStore) GetAllForExportAfter(limit int, afterID string) ([]*model.T return r0, r1 } -// GetAllPage provides a mock function with given fields: offset, limit -func (_m *TeamStore) GetAllPage(offset int, limit int) ([]*model.Team, error) { - ret := _m.Called(offset, limit) +// GetAllPage provides a mock function with given fields: offset, limit, opts +func (_m *TeamStore) GetAllPage(offset int, limit int, opts *model.TeamSearch) ([]*model.Team, error) { + ret := _m.Called(offset, limit, opts) var r0 []*model.Team - if rf, ok := ret.Get(0).(func(int, int) []*model.Team); ok { - r0 = rf(offset, limit) + if rf, ok := ret.Get(0).(func(int, int, *model.TeamSearch) []*model.Team); ok { + r0 = rf(offset, limit, opts) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]*model.Team) @@ -223,8 +181,8 @@ func (_m *TeamStore) GetAllPage(offset int, limit int) ([]*model.Team, error) { } var r1 error - if rf, ok := ret.Get(1).(func(int, int) error); ok { - r1 = rf(offset, limit) + if rf, ok := ret.Get(1).(func(int, int, *model.TeamSearch) error); ok { + r1 = rf(offset, limit, opts) } else { r1 = ret.Error(1) } @@ -255,52 +213,6 @@ func (_m *TeamStore) GetAllPrivateTeamListing() ([]*model.Team, error) { return r0, r1 } -// GetAllPrivateTeamPageListing provides a mock function with given fields: offset, limit -func (_m *TeamStore) GetAllPrivateTeamPageListing(offset int, limit int) ([]*model.Team, error) { - ret := _m.Called(offset, limit) - - var r0 []*model.Team - if rf, ok := ret.Get(0).(func(int, int) []*model.Team); ok { - r0 = rf(offset, limit) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]*model.Team) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(int, int) error); ok { - r1 = rf(offset, limit) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetAllPublicTeamPageListing provides a mock function with given fields: offset, limit -func (_m *TeamStore) GetAllPublicTeamPageListing(offset int, limit int) ([]*model.Team, error) { - ret := _m.Called(offset, limit) - - var r0 []*model.Team - if rf, ok := ret.Get(0).(func(int, int) []*model.Team); ok { - r0 = rf(offset, limit) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]*model.Team) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(int, int) error); ok { - r1 = rf(offset, limit) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // GetAllTeamListing provides a mock function with given fields: func (_m *TeamStore) GetAllTeamListing() ([]*model.Team, error) { ret := _m.Called() @@ -324,29 +236,6 @@ func (_m *TeamStore) GetAllTeamListing() ([]*model.Team, error) { return r0, r1 } -// GetAllTeamPageListing provides a mock function with given fields: offset, limit -func (_m *TeamStore) GetAllTeamPageListing(offset int, limit int) ([]*model.Team, error) { - ret := _m.Called(offset, limit) - - var r0 []*model.Team - if rf, ok := ret.Get(0).(func(int, int) []*model.Team); ok { - r0 = rf(offset, limit) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]*model.Team) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(int, int) error); ok { - r1 = rf(offset, limit) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // GetByInviteId provides a mock function with given fields: inviteID func (_m *TeamStore) GetByInviteId(inviteID string) (*model.Team, error) { ret := _m.Called(inviteID) @@ -892,13 +781,13 @@ func (_m *TeamStore) SaveMultipleMembers(members []*model.TeamMember, maxUsersPe return r0, r1 } -// SearchAll provides a mock function with given fields: term, opts -func (_m *TeamStore) SearchAll(term string, opts *model.TeamSearch) ([]*model.Team, error) { - ret := _m.Called(term, opts) +// SearchAll provides a mock function with given fields: opts +func (_m *TeamStore) SearchAll(opts *model.TeamSearch) ([]*model.Team, error) { + ret := _m.Called(opts) var r0 []*model.Team - if rf, ok := ret.Get(0).(func(string, *model.TeamSearch) []*model.Team); ok { - r0 = rf(term, opts) + if rf, ok := ret.Get(0).(func(*model.TeamSearch) []*model.Team); ok { + r0 = rf(opts) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]*model.Team) @@ -906,8 +795,8 @@ func (_m *TeamStore) SearchAll(term string, opts *model.TeamSearch) ([]*model.Te } var r1 error - if rf, ok := ret.Get(1).(func(string, *model.TeamSearch) error); ok { - r1 = rf(term, opts) + if rf, ok := ret.Get(1).(func(*model.TeamSearch) error); ok { + r1 = rf(opts) } else { r1 = ret.Error(1) } @@ -915,13 +804,13 @@ func (_m *TeamStore) SearchAll(term string, opts *model.TeamSearch) ([]*model.Te return r0, r1 } -// SearchAllPaged provides a mock function with given fields: term, opts -func (_m *TeamStore) SearchAllPaged(term string, opts *model.TeamSearch) ([]*model.Team, int64, error) { - ret := _m.Called(term, opts) +// SearchAllPaged provides a mock function with given fields: opts +func (_m *TeamStore) SearchAllPaged(opts *model.TeamSearch) ([]*model.Team, int64, error) { + ret := _m.Called(opts) var r0 []*model.Team - if rf, ok := ret.Get(0).(func(string, *model.TeamSearch) []*model.Team); ok { - r0 = rf(term, opts) + if rf, ok := ret.Get(0).(func(*model.TeamSearch) []*model.Team); ok { + r0 = rf(opts) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]*model.Team) @@ -929,15 +818,15 @@ func (_m *TeamStore) SearchAllPaged(term string, opts *model.TeamSearch) ([]*mod } var r1 int64 - if rf, ok := ret.Get(1).(func(string, *model.TeamSearch) int64); ok { - r1 = rf(term, opts) + if rf, ok := ret.Get(1).(func(*model.TeamSearch) int64); ok { + r1 = rf(opts) } else { r1 = ret.Get(1).(int64) } var r2 error - if rf, ok := ret.Get(2).(func(string, *model.TeamSearch) error); ok { - r2 = rf(term, opts) + if rf, ok := ret.Get(2).(func(*model.TeamSearch) error); ok { + r2 = rf(opts) } else { r2 = ret.Error(2) } @@ -945,13 +834,13 @@ func (_m *TeamStore) SearchAllPaged(term string, opts *model.TeamSearch) ([]*mod return r0, r1, r2 } -// SearchOpen provides a mock function with given fields: term -func (_m *TeamStore) SearchOpen(term string) ([]*model.Team, error) { - ret := _m.Called(term) +// SearchOpen provides a mock function with given fields: opts +func (_m *TeamStore) SearchOpen(opts *model.TeamSearch) ([]*model.Team, error) { + ret := _m.Called(opts) var r0 []*model.Team - if rf, ok := ret.Get(0).(func(string) []*model.Team); ok { - r0 = rf(term) + if rf, ok := ret.Get(0).(func(*model.TeamSearch) []*model.Team); ok { + r0 = rf(opts) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]*model.Team) @@ -959,8 +848,8 @@ func (_m *TeamStore) SearchOpen(term string) ([]*model.Team, error) { } var r1 error - if rf, ok := ret.Get(1).(func(string) error); ok { - r1 = rf(term) + if rf, ok := ret.Get(1).(func(*model.TeamSearch) error); ok { + r1 = rf(opts) } else { r1 = ret.Error(1) } @@ -968,13 +857,13 @@ func (_m *TeamStore) SearchOpen(term string) ([]*model.Team, error) { return r0, r1 } -// SearchPrivate provides a mock function with given fields: term -func (_m *TeamStore) SearchPrivate(term string) ([]*model.Team, error) { - ret := _m.Called(term) +// SearchPrivate provides a mock function with given fields: opts +func (_m *TeamStore) SearchPrivate(opts *model.TeamSearch) ([]*model.Team, error) { + ret := _m.Called(opts) var r0 []*model.Team - if rf, ok := ret.Get(0).(func(string) []*model.Team); ok { - r0 = rf(term) + if rf, ok := ret.Get(0).(func(*model.TeamSearch) []*model.Team); ok { + r0 = rf(opts) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]*model.Team) @@ -982,8 +871,8 @@ func (_m *TeamStore) SearchPrivate(term string) ([]*model.Team, error) { } var r1 error - if rf, ok := ret.Get(1).(func(string) error); ok { - r1 = rf(term) + if rf, ok := ret.Get(1).(func(*model.TeamSearch) error); ok { + r1 = rf(opts) } else { r1 = ret.Error(1) } diff --git a/store/storetest/retention_policy_store.go b/store/storetest/retention_policy_store.go new file mode 100644 index 0000000000..6fcc22d506 --- /dev/null +++ b/store/storetest/retention_policy_store.go @@ -0,0 +1,658 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +package storetest + +import ( + "sort" + "strconv" + "testing" + + "github.com/mattermost/mattermost-server/v5/model" + "github.com/mattermost/mattermost-server/v5/store" + "github.com/stretchr/testify/require" +) + +func TestRetentionPolicyStore(t *testing.T, ss store.Store, s SqlStore) { + t.Run("Save", func(t *testing.T) { testRetentionPolicyStoreSave(t, ss, s) }) + t.Run("Patch", func(t *testing.T) { testRetentionPolicyStorePatch(t, ss, s) }) + t.Run("Get", func(t *testing.T) { testRetentionPolicyStoreGet(t, ss, s) }) + t.Run("GetCount", func(t *testing.T) { testRetentionPolicyStoreGetCount(t, ss, s) }) + t.Run("Delete", func(t *testing.T) { testRetentionPolicyStoreDelete(t, ss, s) }) + t.Run("GetChannels", func(t *testing.T) { testRetentionPolicyStoreGetChannels(t, ss, s) }) + t.Run("AddChannels", func(t *testing.T) { testRetentionPolicyStoreAddChannels(t, ss, s) }) + t.Run("RemoveChannels", func(t *testing.T) { testRetentionPolicyStoreRemoveChannels(t, ss, s) }) + t.Run("GetTeams", func(t *testing.T) { testRetentionPolicyStoreGetTeams(t, ss, s) }) + t.Run("AddTeams", func(t *testing.T) { testRetentionPolicyStoreAddTeams(t, ss, s) }) + t.Run("RemoveTeams", func(t *testing.T) { testRetentionPolicyStoreRemoveTeams(t, ss, s) }) + t.Run("GetPoliciesForUser", func(t *testing.T) { testRetentionPolicyStoreGetPoliciesForUser(t, ss, s) }) +} + +func getRetentionPolicyWithTeamAndChannelIds(t *testing.T, ss store.Store, policyID string) *model.RetentionPolicyWithTeamAndChannelIDs { + policyWithCounts, err := ss.RetentionPolicy().Get(policyID) + require.NoError(t, err) + policyWithIds := model.RetentionPolicyWithTeamAndChannelIDs{ + RetentionPolicy: model.RetentionPolicy{ + ID: policyID, + DisplayName: policyWithCounts.DisplayName, + PostDuration: policyWithCounts.PostDuration, + }, + ChannelIDs: make([]string, int(policyWithCounts.ChannelCount)), + TeamIDs: make([]string, int(policyWithCounts.TeamCount)), + } + channels, err := ss.RetentionPolicy().GetChannels(policyID, 0, 1000) + require.NoError(t, err) + for i, channel := range channels { + policyWithIds.ChannelIDs[i] = channel.Id + } + teams, err := ss.RetentionPolicy().GetTeams(policyID, 0, 1000) + require.NoError(t, err) + for i, team := range teams { + policyWithIds.TeamIDs[i] = team.Id + } + return &policyWithIds +} + +func CheckRetentionPolicyWithTeamAndChannelIdsAreEqual(t *testing.T, p1, p2 *model.RetentionPolicyWithTeamAndChannelIDs) { + require.Equal(t, p1.ID, p2.ID) + require.Equal(t, p1.DisplayName, p2.DisplayName) + require.Equal(t, p1.PostDuration, p2.PostDuration) + require.Equal(t, len(p1.ChannelIDs), len(p2.ChannelIDs)) + if p1.ChannelIDs == nil || p2.ChannelIDs == nil { + require.Equal(t, p1.ChannelIDs, p2.ChannelIDs) + } else { + sort.Strings(p1.ChannelIDs) + sort.Strings(p2.ChannelIDs) + } + for i := range p1.ChannelIDs { + require.Equal(t, p1.ChannelIDs[i], p2.ChannelIDs[i]) + } + if p1.TeamIDs == nil || p2.TeamIDs == nil { + require.Equal(t, p1.TeamIDs, p2.TeamIDs) + } else { + sort.Strings(p1.TeamIDs) + sort.Strings(p2.TeamIDs) + } + require.Equal(t, len(p1.TeamIDs), len(p2.TeamIDs)) + for i := range p1.TeamIDs { + require.Equal(t, p1.TeamIDs[i], p2.TeamIDs[i]) + } +} + +func CheckRetentionPolicyWithTeamAndChannelCountsAreEqual(t *testing.T, p1, p2 *model.RetentionPolicyWithTeamAndChannelCounts) { + require.Equal(t, p1.ID, p2.ID) + require.Equal(t, p1.DisplayName, p2.DisplayName) + require.Equal(t, p1.PostDuration, p2.PostDuration) + require.Equal(t, p1.ChannelCount, p2.ChannelCount) + require.Equal(t, p1.TeamCount, p2.TeamCount) +} + +func checkRetentionPolicyLikeThisExists(t *testing.T, ss store.Store, expected *model.RetentionPolicyWithTeamAndChannelIDs) { + retrieved := getRetentionPolicyWithTeamAndChannelIds(t, ss, expected.ID) + CheckRetentionPolicyWithTeamAndChannelIdsAreEqual(t, expected, retrieved) +} + +func copyRetentionPolicyWithTeamAndChannelIds(policy *model.RetentionPolicyWithTeamAndChannelIDs) *model.RetentionPolicyWithTeamAndChannelIDs { + copy := &model.RetentionPolicyWithTeamAndChannelIDs{ + RetentionPolicy: policy.RetentionPolicy, + ChannelIDs: make([]string, len(policy.ChannelIDs)), + TeamIDs: make([]string, len(policy.TeamIDs)), + } + for i, channelID := range policy.ChannelIDs { + copy.ChannelIDs[i] = channelID + } + for i, teamID := range policy.TeamIDs { + copy.TeamIDs[i] = teamID + } + return copy +} + +func createChannelsForRetentionPolicy(t *testing.T, ss store.Store, teamId string, numChannels int) (channelIDs []string) { + channelIDs = make([]string, numChannels) + for i := range channelIDs { + name := "channel" + model.NewId() + channel := &model.Channel{ + TeamId: teamId, + DisplayName: "Channel " + name, + Name: name, + Type: model.CHANNEL_OPEN, + } + channel, err := ss.Channel().Save(channel, -1) + require.NoError(t, err) + channelIDs[i] = channel.Id + } + return +} + +func createTeamsForRetentionPolicy(t *testing.T, ss store.Store, numTeams int) (teamIDs []string) { + teamIDs = make([]string, numTeams) + for i := range teamIDs { + name := "team" + model.NewId() + team := &model.Team{ + DisplayName: "Team " + name, + Name: name, + Type: model.TEAM_OPEN, + } + team, err := ss.Team().Save(team) + require.NoError(t, err) + teamIDs[i] = team.Id + } + return +} + +func createTeamsAndChannelsForRetentionPolicy(t *testing.T, ss store.Store) (teamIDs, channelIDs []string) { + teamIDs = createTeamsForRetentionPolicy(t, ss, 2) + channels1 := createChannelsForRetentionPolicy(t, ss, teamIDs[0], 1) + channels2 := createChannelsForRetentionPolicy(t, ss, teamIDs[1], 2) + channelIDs = append(channels1, channels2...) + return +} + +func cleanupRetentionPolicyTest(s SqlStore) { + // Manually clear tables until testlib can handle cleanups + tables := []string{"RetentionPolicies", "RetentionPoliciesChannels", "RetentionPoliciesTeams"} + for _, table := range tables { + if _, err := s.GetMaster().Exec("DELETE FROM " + table); err != nil { + panic(err) + } + } +} + +func deleteTeamsAndChannels(ss store.Store, teamIDs, channelIDs []string) { + for _, teamID := range teamIDs { + if err := ss.Team().PermanentDelete(teamID); err != nil { + panic(err) + } + } + for _, channelID := range channelIDs { + if err := ss.Channel().PermanentDelete(channelID); err != nil { + panic(err) + } + } +} + +func createRetentionPolicyWithTeamAndChannelIds(displayName string, teamIDs, channelIDs []string) *model.RetentionPolicyWithTeamAndChannelIDs { + return &model.RetentionPolicyWithTeamAndChannelIDs{ + RetentionPolicy: model.RetentionPolicy{ + DisplayName: displayName, + PostDuration: model.NewInt64(30), + }, + TeamIDs: teamIDs, + ChannelIDs: channelIDs, + } +} + +// saveRetentionPolicyWithTeamAndChannelIds creates a model.RetentionPolicyWithTeamAndChannelIds struct using +// the display name, team IDs, and channel IDs. The new policy ID will be assigned to the struct and returned. +// The team IDs and channel IDs are kept the same. +func saveRetentionPolicyWithTeamAndChannelIds(t *testing.T, ss store.Store, displayName string, teamIDs, channelIDs []string) *model.RetentionPolicyWithTeamAndChannelIDs { + proposal := createRetentionPolicyWithTeamAndChannelIds(displayName, teamIDs, channelIDs) + policyWithCounts, err := ss.RetentionPolicy().Save(proposal) + require.NoError(t, err) + proposal.ID = policyWithCounts.ID + return proposal +} + +func restoreRetentionPolicy(t *testing.T, ss store.Store, policy *model.RetentionPolicyWithTeamAndChannelIDs) { + _, err := ss.RetentionPolicy().Patch(policy) + require.NoError(t, err) + checkRetentionPolicyLikeThisExists(t, ss, policy) +} + +func testRetentionPolicyStoreSave(t *testing.T, ss store.Store, s SqlStore) { + defer cleanupRetentionPolicyTest(s) + + t.Run("teams and channels are nil", func(t *testing.T) { + policy := saveRetentionPolicyWithTeamAndChannelIds(t, ss, "Policy 1", nil, nil) + policy.ChannelIDs = []string{} + policy.TeamIDs = []string{} + checkRetentionPolicyLikeThisExists(t, ss, policy) + }) + t.Run("teams and channels are empty", func(t *testing.T) { + policy := saveRetentionPolicyWithTeamAndChannelIds(t, ss, "Policy 2", []string{}, []string{}) + checkRetentionPolicyLikeThisExists(t, ss, policy) + }) + t.Run("some teams and channels are specified", func(t *testing.T) { + teamIDs, channelIDs := createTeamsAndChannelsForRetentionPolicy(t, ss) + defer deleteTeamsAndChannels(ss, teamIDs, channelIDs) + policy := saveRetentionPolicyWithTeamAndChannelIds(t, ss, "Policy 3", teamIDs, channelIDs) + checkRetentionPolicyLikeThisExists(t, ss, policy) + }) + t.Run("team specified does not exist", func(t *testing.T) { + policy := createRetentionPolicyWithTeamAndChannelIds("Policy 4", []string{"no_such_team"}, []string{}) + _, err := ss.RetentionPolicy().Save(policy) + require.Error(t, err) + }) + t.Run("channel specified does not exist", func(t *testing.T) { + policy := createRetentionPolicyWithTeamAndChannelIds("Policy 5", []string{}, []string{"no_such_channel"}) + _, err := ss.RetentionPolicy().Save(policy) + require.Error(t, err) + }) +} + +func testRetentionPolicyStorePatch(t *testing.T, ss store.Store, s SqlStore) { + teamIDs, channelIDs := createTeamsAndChannelsForRetentionPolicy(t, ss) + policy := saveRetentionPolicyWithTeamAndChannelIds(t, ss, "Policy 1", teamIDs, channelIDs) + + defer deleteTeamsAndChannels(ss, teamIDs, channelIDs) + defer cleanupRetentionPolicyTest(s) + + t.Run("modify DisplayName", func(t *testing.T) { + patch := &model.RetentionPolicyWithTeamAndChannelIDs{ + RetentionPolicy: model.RetentionPolicy{ + ID: policy.ID, + DisplayName: "something new", + }, + } + _, err := ss.RetentionPolicy().Patch(patch) + require.NoError(t, err) + expected := copyRetentionPolicyWithTeamAndChannelIds(policy) + expected.DisplayName = patch.DisplayName + checkRetentionPolicyLikeThisExists(t, ss, expected) + restoreRetentionPolicy(t, ss, policy) + }) + t.Run("modify PostDuration", func(t *testing.T) { + patch := &model.RetentionPolicyWithTeamAndChannelIDs{ + RetentionPolicy: model.RetentionPolicy{ + ID: policy.ID, + PostDuration: model.NewInt64(10000), + }, + } + _, err := ss.RetentionPolicy().Patch(patch) + require.NoError(t, err) + expected := copyRetentionPolicyWithTeamAndChannelIds(policy) + expected.PostDuration = patch.PostDuration + checkRetentionPolicyLikeThisExists(t, ss, expected) + + // Store a negative value (= infinity) + patch.PostDuration = model.NewInt64(-1) + _, err = ss.RetentionPolicy().Patch(patch) + require.NoError(t, err) + expected = copyRetentionPolicyWithTeamAndChannelIds(policy) + expected.PostDuration = patch.PostDuration + checkRetentionPolicyLikeThisExists(t, ss, expected) + + restoreRetentionPolicy(t, ss, policy) + }) + t.Run("clear TeamIds", func(t *testing.T) { + patch := &model.RetentionPolicyWithTeamAndChannelIDs{ + RetentionPolicy: model.RetentionPolicy{ + ID: policy.ID, + }, + TeamIDs: make([]string, 0), + } + _, err := ss.RetentionPolicy().Patch(patch) + require.NoError(t, err) + expected := copyRetentionPolicyWithTeamAndChannelIds(policy) + expected.TeamIDs = make([]string, 0) + checkRetentionPolicyLikeThisExists(t, ss, expected) + restoreRetentionPolicy(t, ss, policy) + }) + t.Run("add team which does not exist", func(t *testing.T) { + patch := &model.RetentionPolicyWithTeamAndChannelIDs{ + RetentionPolicy: model.RetentionPolicy{ + ID: policy.ID, + }, + TeamIDs: []string{"no_such_team"}, + } + _, err := ss.RetentionPolicy().Patch(patch) + require.Error(t, err) + }) + t.Run("clear ChannelIds", func(t *testing.T) { + patch := &model.RetentionPolicyWithTeamAndChannelIDs{ + RetentionPolicy: model.RetentionPolicy{ + ID: policy.ID, + }, + ChannelIDs: make([]string, 0), + } + _, err := ss.RetentionPolicy().Patch(patch) + require.NoError(t, err) + expected := copyRetentionPolicyWithTeamAndChannelIds(policy) + expected.ChannelIDs = make([]string, 0) + checkRetentionPolicyLikeThisExists(t, ss, expected) + restoreRetentionPolicy(t, ss, policy) + }) + t.Run("add channel which does not exist", func(t *testing.T) { + patch := &model.RetentionPolicyWithTeamAndChannelIDs{ + RetentionPolicy: model.RetentionPolicy{ + ID: policy.ID, + }, + ChannelIDs: []string{"no_such_channel"}, + } + _, err := ss.RetentionPolicy().Patch(patch) + require.Error(t, err) + }) +} + +func testRetentionPolicyStoreGet(t *testing.T, ss store.Store, s SqlStore) { + // create multiple policies + policiesWithCounts := make([]*model.RetentionPolicyWithTeamAndChannelCounts, 0) + for i := 0; i < 3; i++ { + teamIDs, channelIDs := createTeamsAndChannelsForRetentionPolicy(t, ss) + defer deleteTeamsAndChannels(ss, teamIDs, channelIDs) + policyWithIds := createRetentionPolicyWithTeamAndChannelIds( + "Policy "+strconv.Itoa(i+1), teamIDs, channelIDs) + policyWithCounts, err := ss.RetentionPolicy().Save(policyWithIds) + require.NoError(t, err) + policiesWithCounts = append(policiesWithCounts, policyWithCounts) + } + defer cleanupRetentionPolicyTest(s) + + t.Run("get all", func(t *testing.T) { + retrievedPolicies, err := ss.RetentionPolicy().GetAll(0, 60) + require.NoError(t, err) + require.Equal(t, len(policiesWithCounts), len(retrievedPolicies)) + for i := range policiesWithCounts { + CheckRetentionPolicyWithTeamAndChannelCountsAreEqual(t, policiesWithCounts[i], retrievedPolicies[i]) + } + }) + t.Run("get all with limit", func(t *testing.T) { + for i := range policiesWithCounts { + retrievedPolicies, err := ss.RetentionPolicy().GetAll(i, 1) + require.NoError(t, err) + require.Equal(t, 1, len(retrievedPolicies)) + CheckRetentionPolicyWithTeamAndChannelCountsAreEqual(t, policiesWithCounts[i], retrievedPolicies[0]) + } + }) + t.Run("get all with same display name", func(t *testing.T) { + for i := 0; i < 5; i++ { + teamIDs, channelIDs := createTeamsAndChannelsForRetentionPolicy(t, ss) + defer deleteTeamsAndChannels(ss, teamIDs, channelIDs) + proposal := createRetentionPolicyWithTeamAndChannelIds( + "Policy Name", teamIDs, channelIDs) + _, err := ss.RetentionPolicy().Save(proposal) + require.NoError(t, err) + } + policies, err := ss.RetentionPolicy().GetAll(0, 60) + require.NoError(t, err) + for i := 1; i < len(policies); i++ { + require.True(t, + policies[i-1].DisplayName < policies[i].DisplayName || + (policies[i-1].DisplayName == policies[i].DisplayName && + policies[i-1].ID < policies[i].ID), + "policies with the same display name should be sorted by ID") + } + }) +} + +func testRetentionPolicyStoreGetCount(t *testing.T, ss store.Store, s SqlStore) { + defer cleanupRetentionPolicyTest(s) + + t.Run("no policies", func(t *testing.T) { + count, err := ss.RetentionPolicy().GetCount() + require.NoError(t, err) + require.Equal(t, int64(0), count) + }) + t.Run("some policies", func(t *testing.T) { + for i := 0; i < 2; i++ { + saveRetentionPolicyWithTeamAndChannelIds(t, ss, "Policy "+strconv.Itoa(i), nil, nil) + } + count, err := ss.RetentionPolicy().GetCount() + require.NoError(t, err) + require.Equal(t, int64(2), count) + }) +} + +func testRetentionPolicyStoreDelete(t *testing.T, ss store.Store, s SqlStore) { + teamIDs, channelIDs := createTeamsAndChannelsForRetentionPolicy(t, ss) + policy := saveRetentionPolicyWithTeamAndChannelIds(t, ss, "Policy 1", teamIDs, channelIDs) + + defer deleteTeamsAndChannels(ss, teamIDs, channelIDs) + defer cleanupRetentionPolicyTest(s) + + t.Run("delete policy", func(t *testing.T) { + err := ss.RetentionPolicy().Delete(policy.ID) + require.NoError(t, err) + policies, err := ss.RetentionPolicy().GetAll(0, 1) + require.NoError(t, err) + require.Empty(t, policies) + }) +} + +func testRetentionPolicyStoreGetChannels(t *testing.T, ss store.Store, s SqlStore) { + defer cleanupRetentionPolicyTest(s) + + t.Run("no channels", func(t *testing.T) { + policy := saveRetentionPolicyWithTeamAndChannelIds(t, ss, "Policy 1", nil, nil) + channels, err := ss.RetentionPolicy().GetChannels(policy.ID, 0, 1) + require.NoError(t, err) + require.Len(t, channels, 0) + }) + t.Run("some channels", func(t *testing.T) { + teamIDs, channelIDs := createTeamsAndChannelsForRetentionPolicy(t, ss) + defer deleteTeamsAndChannels(ss, teamIDs, channelIDs) + policy := saveRetentionPolicyWithTeamAndChannelIds(t, ss, "Policy 2", teamIDs, channelIDs) + channels, err := ss.RetentionPolicy().GetChannels(policy.ID, 0, len(channelIDs)) + require.NoError(t, err) + require.Len(t, channels, len(channelIDs)) + sort.Strings(channelIDs) + sort.Slice(channels, func(i, j int) bool { + return channels[i].Id < channels[j].Id + }) + for i := range channelIDs { + require.Equal(t, channelIDs[i], channels[i].Id) + } + }) +} + +func testRetentionPolicyStoreAddChannels(t *testing.T, ss store.Store, s SqlStore) { + teamIDs, channelIDs := createTeamsAndChannelsForRetentionPolicy(t, ss) + policy := saveRetentionPolicyWithTeamAndChannelIds(t, ss, "Policy 1", teamIDs, channelIDs) + + defer deleteTeamsAndChannels(ss, teamIDs, channelIDs) + defer cleanupRetentionPolicyTest(s) + + t.Run("add empty array", func(t *testing.T) { + err := ss.RetentionPolicy().AddChannels(policy.ID, []string{}) + require.NoError(t, err) + checkRetentionPolicyLikeThisExists(t, ss, policy) + }) + t.Run("add new channels", func(t *testing.T) { + channelIDs := createChannelsForRetentionPolicy(t, ss, teamIDs[0], 2) + defer deleteTeamsAndChannels(ss, nil, channelIDs) + err := ss.RetentionPolicy().AddChannels(policy.ID, channelIDs) + require.NoError(t, err) + // verify that the channels were actually added + copy := copyRetentionPolicyWithTeamAndChannelIds(policy) + copy.ChannelIDs = append(copy.ChannelIDs, channelIDs...) + checkRetentionPolicyLikeThisExists(t, ss, copy) + restoreRetentionPolicy(t, ss, policy) + }) + t.Run("add channel which does not exist", func(t *testing.T) { + err := ss.RetentionPolicy().AddChannels(policy.ID, []string{"no_such_channel"}) + require.Error(t, err) + }) + t.Run("add channel to policy which does not exist", func(t *testing.T) { + channelIDs := createChannelsForRetentionPolicy(t, ss, teamIDs[0], 1) + defer deleteTeamsAndChannels(ss, nil, channelIDs) + err := ss.RetentionPolicy().AddChannels("no_such_policy", channelIDs) + require.Error(t, err) + }) +} + +func testRetentionPolicyStoreRemoveChannels(t *testing.T, ss store.Store, s SqlStore) { + teamIDs, channelIDs := createTeamsAndChannelsForRetentionPolicy(t, ss) + policy := saveRetentionPolicyWithTeamAndChannelIds(t, ss, "Policy 1", teamIDs, channelIDs) + + defer deleteTeamsAndChannels(ss, teamIDs, channelIDs) + defer cleanupRetentionPolicyTest(s) + + t.Run("remove empty array", func(t *testing.T) { + err := ss.RetentionPolicy().RemoveChannels(policy.ID, []string{}) + require.NoError(t, err) + checkRetentionPolicyLikeThisExists(t, ss, policy) + }) + t.Run("remove existing channel", func(t *testing.T) { + channelID := channelIDs[0] + err := ss.RetentionPolicy().RemoveChannels(policy.ID, []string{channelID}) + require.NoError(t, err) + // verify that the channel was actually removed + copy := copyRetentionPolicyWithTeamAndChannelIds(policy) + copy.ChannelIDs = make([]string, 0) + for _, oldChannelID := range policy.ChannelIDs { + if oldChannelID != channelID { + copy.ChannelIDs = append(copy.ChannelIDs, oldChannelID) + } + } + checkRetentionPolicyLikeThisExists(t, ss, copy) + restoreRetentionPolicy(t, ss, policy) + }) + t.Run("remove channel which does not exist", func(t *testing.T) { + err := ss.RetentionPolicy().RemoveChannels(policy.ID, []string{"no_such_channel"}) + require.NoError(t, err) + // verify that the policy did not change + checkRetentionPolicyLikeThisExists(t, ss, policy) + }) +} + +func testRetentionPolicyStoreGetTeams(t *testing.T, ss store.Store, s SqlStore) { + defer cleanupRetentionPolicyTest(s) + + t.Run("no teams", func(t *testing.T) { + policy := saveRetentionPolicyWithTeamAndChannelIds(t, ss, "Policy 1", nil, nil) + teams, err := ss.RetentionPolicy().GetTeams(policy.ID, 0, 1) + require.NoError(t, err) + require.Len(t, teams, 0) + }) + t.Run("some teams", func(t *testing.T) { + teamIDs, channelIDs := createTeamsAndChannelsForRetentionPolicy(t, ss) + defer deleteTeamsAndChannels(ss, teamIDs, channelIDs) + policy := saveRetentionPolicyWithTeamAndChannelIds(t, ss, "Policy 2", teamIDs, channelIDs) + teams, err := ss.RetentionPolicy().GetTeams(policy.ID, 0, len(teamIDs)) + require.NoError(t, err) + require.Len(t, teams, len(teamIDs)) + sort.Strings(teamIDs) + sort.Slice(teams, func(i, j int) bool { + return teams[i].Id < teams[j].Id + }) + for i := range teamIDs { + require.Equal(t, teamIDs[i], teams[i].Id) + } + }) +} + +func testRetentionPolicyStoreAddTeams(t *testing.T, ss store.Store, s SqlStore) { + teamIDs, channelIDs := createTeamsAndChannelsForRetentionPolicy(t, ss) + policy := saveRetentionPolicyWithTeamAndChannelIds(t, ss, "Policy 1", teamIDs, channelIDs) + + defer deleteTeamsAndChannels(ss, teamIDs, channelIDs) + defer cleanupRetentionPolicyTest(s) + + t.Run("add empty array", func(t *testing.T) { + err := ss.RetentionPolicy().AddTeams(policy.ID, []string{}) + require.NoError(t, err) + checkRetentionPolicyLikeThisExists(t, ss, policy) + }) + t.Run("add new teams", func(t *testing.T) { + teamIDs := createTeamsForRetentionPolicy(t, ss, 2) + defer deleteTeamsAndChannels(ss, teamIDs, nil) + err := ss.RetentionPolicy().AddTeams(policy.ID, teamIDs) + require.NoError(t, err) + // verify that the teams were actually added + copy := copyRetentionPolicyWithTeamAndChannelIds(policy) + copy.TeamIDs = append(copy.TeamIDs, teamIDs...) + checkRetentionPolicyLikeThisExists(t, ss, copy) + restoreRetentionPolicy(t, ss, policy) + }) + t.Run("add team which does not exist", func(t *testing.T) { + err := ss.RetentionPolicy().AddTeams(policy.ID, []string{"no_such_team"}) + require.Error(t, err) + }) + t.Run("add team to policy which does not exist", func(t *testing.T) { + teamIDs := createTeamsForRetentionPolicy(t, ss, 1) + defer deleteTeamsAndChannels(ss, teamIDs, nil) + err := ss.RetentionPolicy().AddTeams("no_such_policy", teamIDs) + require.Error(t, err) + }) +} + +func testRetentionPolicyStoreRemoveTeams(t *testing.T, ss store.Store, s SqlStore) { + teamIDs, channelIDs := createTeamsAndChannelsForRetentionPolicy(t, ss) + policy := saveRetentionPolicyWithTeamAndChannelIds(t, ss, "Policy 1", teamIDs, channelIDs) + + defer deleteTeamsAndChannels(ss, teamIDs, channelIDs) + defer cleanupRetentionPolicyTest(s) + + t.Run("remove empty array", func(t *testing.T) { + err := ss.RetentionPolicy().RemoveTeams(policy.ID, []string{}) + require.NoError(t, err) + checkRetentionPolicyLikeThisExists(t, ss, policy) + }) + t.Run("remove existing team", func(t *testing.T) { + teamID := teamIDs[0] + err := ss.RetentionPolicy().RemoveTeams(policy.ID, []string{teamID}) + require.NoError(t, err) + // verify that the team was actually removed + copy := copyRetentionPolicyWithTeamAndChannelIds(policy) + copy.TeamIDs = make([]string, 0) + for _, oldTeamID := range policy.TeamIDs { + if oldTeamID != teamID { + copy.TeamIDs = append(copy.TeamIDs, oldTeamID) + } + } + checkRetentionPolicyLikeThisExists(t, ss, copy) + restoreRetentionPolicy(t, ss, policy) + }) + t.Run("remove team which does not exist", func(t *testing.T) { + err := ss.RetentionPolicy().RemoveTeams(policy.ID, []string{"no_such_team"}) + require.NoError(t, err) + // verify that the policy did not change + checkRetentionPolicyLikeThisExists(t, ss, policy) + }) +} + +func testRetentionPolicyStoreGetPoliciesForUser(t *testing.T, ss store.Store, s SqlStore) { + teamIDs, channelIDs := createTeamsAndChannelsForRetentionPolicy(t, ss) + saveRetentionPolicyWithTeamAndChannelIds(t, ss, "Policy 1", teamIDs, channelIDs) + + defer deleteTeamsAndChannels(ss, teamIDs, channelIDs) + defer cleanupRetentionPolicyTest(s) + + user, userSaveErr := ss.User().Save(&model.User{ + Email: MakeEmail(), + Username: model.NewId(), + }) + require.NoError(t, userSaveErr) + + t.Run("user has no relevant policies", func(t *testing.T) { + // Teams + teamPolicies, err := ss.RetentionPolicy().GetTeamPoliciesForUser(user.Id, 0, 100) + require.NoError(t, err) + require.Empty(t, teamPolicies) + count, err := ss.RetentionPolicy().GetTeamPoliciesCountForUser(user.Id) + require.NoError(t, err) + require.Equal(t, int64(0), count) + // Channels + channelPolicies, err := ss.RetentionPolicy().GetChannelPoliciesForUser(user.Id, 0, 100) + require.NoError(t, err) + require.Empty(t, channelPolicies) + count, err = ss.RetentionPolicy().GetChannelPoliciesCountForUser(user.Id) + require.NoError(t, err) + require.Equal(t, int64(0), count) + }) + + t.Run("user has relevant policies", func(t *testing.T) { + for _, teamID := range teamIDs { + _, err := ss.Team().SaveMember(&model.TeamMember{TeamId: teamID, UserId: user.Id}, -1) + require.NoError(t, err) + } + for _, channelID := range channelIDs { + _, err := ss.Channel().SaveMember(&model.ChannelMember{ChannelId: channelID, UserId: user.Id, NotifyProps: model.GetDefaultChannelNotifyProps()}) + require.NoError(t, err) + } + // Teams + teamPolicies, err := ss.RetentionPolicy().GetTeamPoliciesForUser(user.Id, 0, 100) + require.NoError(t, err) + require.Len(t, teamPolicies, len(teamIDs)) + count, err := ss.RetentionPolicy().GetTeamPoliciesCountForUser(user.Id) + require.NoError(t, err) + require.Equal(t, int64(len(teamIDs)), count) + // Channels + channelPolicies, err := ss.RetentionPolicy().GetChannelPoliciesForUser(user.Id, 0, 100) + require.NoError(t, err) + require.Len(t, channelPolicies, len(channelIDs)) + count, err = ss.RetentionPolicy().GetChannelPoliciesCountForUser(user.Id) + require.NoError(t, err) + require.Equal(t, int64(len(channelIDs)), count) + }) +} diff --git a/store/storetest/shared_channel_store.go b/store/storetest/shared_channel_store.go index 2a74acbf7d..0182d484cd 100644 --- a/store/storetest/shared_channel_store.go +++ b/store/storetest/shared_channel_store.go @@ -181,7 +181,7 @@ func testHasSharedChannel(t *testing.T, ss store.Store) { } func testGetSharedChannels(t *testing.T, ss store.Store) { - clearSharedChannels(ss) + require.NoError(t, clearSharedChannels(ss)) creator := model.NewId() team1 := model.NewId() diff --git a/store/storetest/store.go b/store/storetest/store.go index 58a4ef590b..1f0c0a181f 100644 --- a/store/storetest/store.go +++ b/store/storetest/store.go @@ -20,6 +20,7 @@ type Store struct { ChannelStore mocks.ChannelStore PostStore mocks.PostStore UserStore mocks.UserStore + RetentionPolicyStore mocks.RetentionPolicyStore BotStore mocks.BotStore AuditStore mocks.AuditStore ClusterDiscoveryStore mocks.ClusterDiscoveryStore @@ -61,6 +62,7 @@ func (s *Store) Team() store.TeamStore { return &s.T func (s *Store) Channel() store.ChannelStore { return &s.ChannelStore } func (s *Store) Post() store.PostStore { return &s.PostStore } func (s *Store) User() store.UserStore { return &s.UserStore } +func (s *Store) RetentionPolicy() store.RetentionPolicyStore { return &s.RetentionPolicyStore } func (s *Store) Bot() store.BotStore { return &s.BotStore } func (s *Store) ProductNotices() store.ProductNoticesStore { return &s.ProductNoticesStore } func (s *Store) Audit() store.AuditStore { return &s.AuditStore } diff --git a/store/storetest/team_store.go b/store/storetest/team_store.go index 3e03b76bdf..2971c4d87e 100644 --- a/store/storetest/team_store.go +++ b/store/storetest/team_store.go @@ -39,6 +39,7 @@ func TestTeamStore(t *testing.T, ss store.Store) { t.Run("GetByInviteId", func(t *testing.T) { testTeamStoreGetByInviteId(t, ss) }) t.Run("ByUserId", func(t *testing.T) { testTeamStoreByUserId(t, ss) }) t.Run("GetAllTeamListing", func(t *testing.T) { testGetAllTeamListing(t, ss) }) + t.Run("GetAllTeamPage", func(t *testing.T) { testTeamStoreGetAllPage(t, ss) }) t.Run("GetAllTeamPageListing", func(t *testing.T) { testGetAllTeamPageListing(t, ss) }) t.Run("GetAllPrivateTeamListing", func(t *testing.T) { testGetAllPrivateTeamListing(t, ss) }) t.Run("GetAllPrivateTeamPageListing", func(t *testing.T) { testGetAllPrivateTeamPageListing(t, ss) }) @@ -213,6 +214,8 @@ func testTeamStoreGetByName(t *testing.T, ss store.Store) { } func testTeamStoreSearchAll(t *testing.T, ss store.Store) { + cleanupTeamStore(t, ss) + o := model.Team{} o.DisplayName = "ADisplayName" + NewTestId() o.Name = "searchterm-" + NewTestId() @@ -244,14 +247,23 @@ func testTeamStoreSearchAll(t *testing.T, ss store.Store) { _, err = ss.Team().Save(&g) require.NoError(t, err) - q := model.Team{} + q := &model.Team{} q.DisplayName = "CHOCOLATE" q.Name = "ilovecake" q.Email = MakeEmail() q.Type = model.TEAM_OPEN q.AllowOpenInvite = false - _, err = ss.Team().Save(&q) + q, err = ss.Team().Save(q) + require.NoError(t, err) + + _, err = ss.RetentionPolicy().Save(&model.RetentionPolicyWithTeamAndChannelIDs{ + RetentionPolicy: model.RetentionPolicy{ + DisplayName: "Policy 1", + PostDuration: model.NewInt64(20), + }, + TeamIDs: []string{q.Id}, + }) require.NoError(t, err) testCases := []struct { @@ -368,11 +380,17 @@ func testTeamStoreSearchAll(t *testing.T, ss store.Store) { 2, []string{p.Id, o.Id}, }, + { + "Search for teams which are not part of a data retention policy", + &model.TeamSearch{Term: "", ExcludePolicyConstrained: model.NewBool(true)}, + 3, + []string{o.Id, p.Id, g.Id}, + }, } for _, tc := range testCases { t.Run(tc.Name, func(t *testing.T) { - response, err := ss.Team().SearchAll(tc.Opts.Term, tc.Opts) + response, err := ss.Team().SearchAll(tc.Opts) require.NoError(t, err) require.Equal(t, tc.ExpectedLenth, len(response)) responseTeamIds := []string{} @@ -479,7 +497,7 @@ func testTeamStoreSearchOpen(t *testing.T, ss store.Store) { for _, tc := range testCases { t.Run(tc.Name, func(t *testing.T) { - r1, err := ss.Team().SearchOpen(tc.Term) + r1, err := ss.Team().SearchOpen(&model.TeamSearch{Term: tc.Term}) require.NoError(t, err) results := r1 require.Equal(t, tc.ExpectedLength, len(results)) @@ -585,7 +603,7 @@ func testTeamStoreSearchPrivate(t *testing.T, ss store.Store) { for _, tc := range testCases { t.Run(tc.Name, func(t *testing.T) { - r1, err := ss.Team().SearchPrivate(tc.Term) + r1, err := ss.Team().SearchPrivate(&model.TeamSearch{Term: tc.Term}) require.NoError(t, err) results := r1 require.Equal(t, tc.ExpectedLength, len(results)) @@ -641,6 +659,64 @@ func testTeamStoreByUserId(t *testing.T, ss store.Store) { require.Equal(t, teams[0].Id, o1.Id, "should be a member") } +func testTeamStoreGetAllPage(t *testing.T, ss store.Store) { + o := model.Team{} + o.DisplayName = "ADisplayName" + model.NewId() + o.Name = "zz" + model.NewId() + "a" + o.Email = MakeEmail() + o.Type = model.TEAM_OPEN + o.AllowOpenInvite = true + _, err := ss.Team().Save(&o) + require.NoError(t, err) + + policy, err := ss.RetentionPolicy().Save(&model.RetentionPolicyWithTeamAndChannelIDs{ + RetentionPolicy: model.RetentionPolicy{ + DisplayName: "Policy 1", + PostDuration: model.NewInt64(30), + }, + TeamIDs: []string{o.Id}, + }) + require.NoError(t, err) + + // Without ExcludePolicyConstrained + teams, err := ss.Team().GetAllPage(0, 100, nil) + require.NoError(t, err) + found := false + for _, team := range teams { + if team.Id == o.Id { + found = true + require.Nil(t, team.PolicyID) + break + } + } + require.True(t, found) + + // With ExcludePolicyConstrained + teams, err = ss.Team().GetAllPage(0, 100, &model.TeamSearch{ExcludePolicyConstrained: model.NewBool(true)}) + require.NoError(t, err) + found = false + for _, team := range teams { + if team.Id == o.Id { + found = true + break + } + } + require.False(t, found) + + // With policy ID + teams, err = ss.Team().GetAllPage(0, 100, &model.TeamSearch{IncludePolicyID: model.NewBool(true)}) + require.NoError(t, err) + found = false + for _, team := range teams { + if team.Id == o.Id { + found = true + require.Equal(t, *team.PolicyID, policy.ID) + break + } + } + require.True(t, found) +} + func testGetAllTeamListing(t *testing.T, ss store.Store) { o1 := model.Team{} o1.DisplayName = "DisplayName" @@ -722,7 +798,9 @@ func testGetAllTeamPageListing(t *testing.T, ss store.Store) { _, err = ss.Team().Save(&o4) require.NoError(t, err) - teams, err := ss.Team().GetAllTeamPageListing(0, 10) + opts := &model.TeamSearch{AllowOpenInvite: model.NewBool(true)} + + teams, err := ss.Team().GetAllPage(0, 10, opts) require.NoError(t, err) for _, team := range teams { @@ -740,7 +818,7 @@ func testGetAllTeamPageListing(t *testing.T, ss store.Store) { _, err = ss.Team().Save(&o5) require.NoError(t, err) - teams, err = ss.Team().GetAllTeamPageListing(0, 4) + teams, err = ss.Team().GetAllPage(0, 4, opts) require.NoError(t, err) for _, team := range teams { @@ -749,7 +827,7 @@ func testGetAllTeamPageListing(t *testing.T, ss store.Store) { require.LessOrEqual(t, len(teams), 4, "should have returned max of 4 teams") - teams, err = ss.Team().GetAllTeamPageListing(1, 1) + teams, err = ss.Team().GetAllPage(1, 1, opts) require.NoError(t, err) for _, team := range teams { @@ -840,7 +918,9 @@ func testGetAllPrivateTeamPageListing(t *testing.T, ss store.Store) { _, err = ss.Team().Save(&o4) require.NoError(t, err) - teams, listErr := ss.Team().GetAllPrivateTeamPageListing(0, 10) + opts := &model.TeamSearch{AllowOpenInvite: model.NewBool(false)} + + teams, listErr := ss.Team().GetAllPage(0, 10, opts) require.NoError(t, listErr) for _, team := range teams { require.False(t, team.AllowOpenInvite, "should have returned team with AllowOpenInvite as false") @@ -857,7 +937,7 @@ func testGetAllPrivateTeamPageListing(t *testing.T, ss store.Store) { _, err = ss.Team().Save(&o5) require.NoError(t, err) - teams, listErr = ss.Team().GetAllPrivateTeamPageListing(0, 4) + teams, listErr = ss.Team().GetAllPage(0, 4, opts) require.NoError(t, listErr) for _, team := range teams { require.False(t, team.AllowOpenInvite, "should have returned team with AllowOpenInvite as false") @@ -865,7 +945,7 @@ func testGetAllPrivateTeamPageListing(t *testing.T, ss store.Store) { require.LessOrEqual(t, len(teams), 4, "should have returned max of 4 teams") - teams, listErr = ss.Team().GetAllPrivateTeamPageListing(1, 1) + teams, listErr = ss.Team().GetAllPage(1, 1, opts) require.NoError(t, listErr) for _, team := range teams { require.False(t, team.AllowOpenInvite, "should have returned team with AllowOpenInvite as false") @@ -913,7 +993,9 @@ func testGetAllPublicTeamPageListing(t *testing.T, ss store.Store) { _, err = ss.Team().Save(&o4) require.NoError(t, err) - teams, err := ss.Team().GetAllPublicTeamPageListing(0, 10) + opts := &model.TeamSearch{AllowOpenInvite: model.NewBool(true)} + + teams, err := ss.Team().GetAllPage(0, 10, opts) assert.NoError(t, err) assert.Equal(t, []*model.Team{t1, t3}, teams) @@ -926,11 +1008,11 @@ func testGetAllPublicTeamPageListing(t *testing.T, ss store.Store) { t5, err := ss.Team().Save(&o5) require.NoError(t, err) - teams, err = ss.Team().GetAllPublicTeamPageListing(0, 4) + teams, err = ss.Team().GetAllPage(0, 4, opts) assert.NoError(t, err) assert.Equal(t, []*model.Team{t1, t3, t5}, teams) - _, err = ss.Team().GetAllPublicTeamPageListing(1, 1) + _, err = ss.Team().GetAllPage(1, 1, opts) assert.NoError(t, err) } @@ -986,7 +1068,7 @@ func testPublicTeamCount(t *testing.T, ss store.Store) { _, err = ss.Team().Save(&o3) require.NoError(t, err) - teamCount, err := ss.Team().AnalyticsPublicTeamCount() + teamCount, err := ss.Team().AnalyticsTeamCount(&model.TeamSearch{AllowOpenInvite: model.NewBool(true)}) require.NoError(t, err) require.Equal(t, int64(2), teamCount, "should only be 1 team") } @@ -1021,7 +1103,7 @@ func testPrivateTeamCount(t *testing.T, ss store.Store) { _, err = ss.Team().Save(&o3) require.NoError(t, err) - teamCount, err := ss.Team().AnalyticsPrivateTeamCount() + teamCount, err := ss.Team().AnalyticsTeamCount(&model.TeamSearch{AllowOpenInvite: model.NewBool(false)}) require.NoError(t, err) require.Equal(t, int64(2), teamCount, "should only be 1 team") } @@ -1037,7 +1119,7 @@ func testTeamCount(t *testing.T, ss store.Store) { require.NoError(t, err) // not including deleted teams - teamCount, err := ss.Team().AnalyticsTeamCount(false) + teamCount, err := ss.Team().AnalyticsTeamCount(nil) require.NoError(t, err) require.NotEqual(t, 0, int(teamCount), "should be at least 1 team") @@ -1047,11 +1129,11 @@ func testTeamCount(t *testing.T, ss store.Store) { require.NoError(t, err) // get the count of teams not including deleted - countNotIncludingDeleted, err := ss.Team().AnalyticsTeamCount(false) + countNotIncludingDeleted, err := ss.Team().AnalyticsTeamCount(nil) require.NoError(t, err) // get the count of teams including deleted - countIncludingDeleted, err := ss.Team().AnalyticsTeamCount(true) + countIncludingDeleted, err := ss.Team().AnalyticsTeamCount(&model.TeamSearch{IncludeDeleted: model.NewBool(true)}) require.NoError(t, err) // count including deleted should be one greater than not including deleted diff --git a/store/timerlayer/timerlayer.go b/store/timerlayer/timerlayer.go index b7ce884bc8..55770bd0be 100644 --- a/store/timerlayer/timerlayer.go +++ b/store/timerlayer/timerlayer.go @@ -39,6 +39,7 @@ type TimerLayer struct { ProductNoticesStore store.ProductNoticesStore ReactionStore store.ReactionStore RemoteClusterStore store.RemoteClusterStore + RetentionPolicyStore store.RetentionPolicyStore RoleStore store.RoleStore SchemeStore store.SchemeStore SessionStore store.SessionStore @@ -140,6 +141,10 @@ func (s *TimerLayer) RemoteCluster() store.RemoteClusterStore { return s.RemoteClusterStore } +func (s *TimerLayer) RetentionPolicy() store.RetentionPolicyStore { + return s.RetentionPolicyStore +} + func (s *TimerLayer) Role() store.RoleStore { return s.RoleStore } @@ -305,6 +310,11 @@ type TimerLayerRemoteClusterStore struct { Root *TimerLayer } +type TimerLayerRetentionPolicyStore struct { + store.RetentionPolicyStore + Root *TimerLayer +} + type TimerLayerRoleStore struct { store.RoleStore Root *TimerLayer @@ -5519,6 +5529,294 @@ func (s *TimerLayerRemoteClusterStore) UpdateTopics(remoteClusterId string, topi return result, err } +func (s *TimerLayerRetentionPolicyStore) AddChannels(policyId string, channelIds []string) error { + start := timemodule.Now() + + err := s.RetentionPolicyStore.AddChannels(policyId, channelIds) + + elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) + if s.Root.Metrics != nil { + success := "false" + if err == nil { + success = "true" + } + s.Root.Metrics.ObserveStoreMethodDuration("RetentionPolicyStore.AddChannels", success, elapsed) + } + return err +} + +func (s *TimerLayerRetentionPolicyStore) AddTeams(policyId string, teamIds []string) error { + start := timemodule.Now() + + err := s.RetentionPolicyStore.AddTeams(policyId, teamIds) + + elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) + if s.Root.Metrics != nil { + success := "false" + if err == nil { + success = "true" + } + s.Root.Metrics.ObserveStoreMethodDuration("RetentionPolicyStore.AddTeams", success, elapsed) + } + return err +} + +func (s *TimerLayerRetentionPolicyStore) Delete(id string) error { + start := timemodule.Now() + + err := s.RetentionPolicyStore.Delete(id) + + elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) + if s.Root.Metrics != nil { + success := "false" + if err == nil { + success = "true" + } + s.Root.Metrics.ObserveStoreMethodDuration("RetentionPolicyStore.Delete", success, elapsed) + } + return err +} + +func (s *TimerLayerRetentionPolicyStore) Get(id string) (*model.RetentionPolicyWithTeamAndChannelCounts, error) { + start := timemodule.Now() + + result, err := s.RetentionPolicyStore.Get(id) + + elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) + if s.Root.Metrics != nil { + success := "false" + if err == nil { + success = "true" + } + s.Root.Metrics.ObserveStoreMethodDuration("RetentionPolicyStore.Get", success, elapsed) + } + return result, err +} + +func (s *TimerLayerRetentionPolicyStore) GetAll(offset int, limit int) ([]*model.RetentionPolicyWithTeamAndChannelCounts, error) { + start := timemodule.Now() + + result, err := s.RetentionPolicyStore.GetAll(offset, limit) + + elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) + if s.Root.Metrics != nil { + success := "false" + if err == nil { + success = "true" + } + s.Root.Metrics.ObserveStoreMethodDuration("RetentionPolicyStore.GetAll", success, elapsed) + } + return result, err +} + +func (s *TimerLayerRetentionPolicyStore) GetChannelPoliciesCountForUser(userID string) (int64, error) { + start := timemodule.Now() + + result, err := s.RetentionPolicyStore.GetChannelPoliciesCountForUser(userID) + + elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) + if s.Root.Metrics != nil { + success := "false" + if err == nil { + success = "true" + } + s.Root.Metrics.ObserveStoreMethodDuration("RetentionPolicyStore.GetChannelPoliciesCountForUser", success, elapsed) + } + return result, err +} + +func (s *TimerLayerRetentionPolicyStore) GetChannelPoliciesForUser(userID string, offset int, limit int) ([]*model.RetentionPolicyForChannel, error) { + start := timemodule.Now() + + result, err := s.RetentionPolicyStore.GetChannelPoliciesForUser(userID, offset, limit) + + elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) + if s.Root.Metrics != nil { + success := "false" + if err == nil { + success = "true" + } + s.Root.Metrics.ObserveStoreMethodDuration("RetentionPolicyStore.GetChannelPoliciesForUser", success, elapsed) + } + return result, err +} + +func (s *TimerLayerRetentionPolicyStore) GetChannels(policyId string, offset int, limit int) (model.ChannelListWithTeamData, error) { + start := timemodule.Now() + + result, err := s.RetentionPolicyStore.GetChannels(policyId, offset, limit) + + elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) + if s.Root.Metrics != nil { + success := "false" + if err == nil { + success = "true" + } + s.Root.Metrics.ObserveStoreMethodDuration("RetentionPolicyStore.GetChannels", success, elapsed) + } + return result, err +} + +func (s *TimerLayerRetentionPolicyStore) GetChannelsCount(policyId string) (int64, error) { + start := timemodule.Now() + + result, err := s.RetentionPolicyStore.GetChannelsCount(policyId) + + elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) + if s.Root.Metrics != nil { + success := "false" + if err == nil { + success = "true" + } + s.Root.Metrics.ObserveStoreMethodDuration("RetentionPolicyStore.GetChannelsCount", success, elapsed) + } + return result, err +} + +func (s *TimerLayerRetentionPolicyStore) GetCount() (int64, error) { + start := timemodule.Now() + + result, err := s.RetentionPolicyStore.GetCount() + + elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) + if s.Root.Metrics != nil { + success := "false" + if err == nil { + success = "true" + } + s.Root.Metrics.ObserveStoreMethodDuration("RetentionPolicyStore.GetCount", success, elapsed) + } + return result, err +} + +func (s *TimerLayerRetentionPolicyStore) GetTeamPoliciesCountForUser(userID string) (int64, error) { + start := timemodule.Now() + + result, err := s.RetentionPolicyStore.GetTeamPoliciesCountForUser(userID) + + elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) + if s.Root.Metrics != nil { + success := "false" + if err == nil { + success = "true" + } + s.Root.Metrics.ObserveStoreMethodDuration("RetentionPolicyStore.GetTeamPoliciesCountForUser", success, elapsed) + } + return result, err +} + +func (s *TimerLayerRetentionPolicyStore) GetTeamPoliciesForUser(userID string, offset int, limit int) ([]*model.RetentionPolicyForTeam, error) { + start := timemodule.Now() + + result, err := s.RetentionPolicyStore.GetTeamPoliciesForUser(userID, offset, limit) + + elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) + if s.Root.Metrics != nil { + success := "false" + if err == nil { + success = "true" + } + s.Root.Metrics.ObserveStoreMethodDuration("RetentionPolicyStore.GetTeamPoliciesForUser", success, elapsed) + } + return result, err +} + +func (s *TimerLayerRetentionPolicyStore) GetTeams(policyId string, offset int, limit int) ([]*model.Team, error) { + start := timemodule.Now() + + result, err := s.RetentionPolicyStore.GetTeams(policyId, offset, limit) + + elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) + if s.Root.Metrics != nil { + success := "false" + if err == nil { + success = "true" + } + s.Root.Metrics.ObserveStoreMethodDuration("RetentionPolicyStore.GetTeams", success, elapsed) + } + return result, err +} + +func (s *TimerLayerRetentionPolicyStore) GetTeamsCount(policyId string) (int64, error) { + start := timemodule.Now() + + result, err := s.RetentionPolicyStore.GetTeamsCount(policyId) + + elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) + if s.Root.Metrics != nil { + success := "false" + if err == nil { + success = "true" + } + s.Root.Metrics.ObserveStoreMethodDuration("RetentionPolicyStore.GetTeamsCount", success, elapsed) + } + return result, err +} + +func (s *TimerLayerRetentionPolicyStore) Patch(patch *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, error) { + start := timemodule.Now() + + result, err := s.RetentionPolicyStore.Patch(patch) + + elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) + if s.Root.Metrics != nil { + success := "false" + if err == nil { + success = "true" + } + s.Root.Metrics.ObserveStoreMethodDuration("RetentionPolicyStore.Patch", success, elapsed) + } + return result, err +} + +func (s *TimerLayerRetentionPolicyStore) RemoveChannels(policyId string, channelIds []string) error { + start := timemodule.Now() + + err := s.RetentionPolicyStore.RemoveChannels(policyId, channelIds) + + elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) + if s.Root.Metrics != nil { + success := "false" + if err == nil { + success = "true" + } + s.Root.Metrics.ObserveStoreMethodDuration("RetentionPolicyStore.RemoveChannels", success, elapsed) + } + return err +} + +func (s *TimerLayerRetentionPolicyStore) RemoveTeams(policyId string, teamIds []string) error { + start := timemodule.Now() + + err := s.RetentionPolicyStore.RemoveTeams(policyId, teamIds) + + elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) + if s.Root.Metrics != nil { + success := "false" + if err == nil { + success = "true" + } + s.Root.Metrics.ObserveStoreMethodDuration("RetentionPolicyStore.RemoveTeams", success, elapsed) + } + return err +} + +func (s *TimerLayerRetentionPolicyStore) Save(policy *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, error) { + start := timemodule.Now() + + result, err := s.RetentionPolicyStore.Save(policy) + + elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) + if s.Root.Metrics != nil { + success := "false" + if err == nil { + success = "true" + } + s.Root.Metrics.ObserveStoreMethodDuration("RetentionPolicyStore.Save", success, elapsed) + } + return result, err +} + func (s *TimerLayerRoleStore) AllChannelSchemeRoles() ([]*model.Role, error) { start := timemodule.Now() @@ -6686,42 +6984,10 @@ func (s *TimerLayerTeamStore) AnalyticsGetTeamCountForScheme(schemeID string) (i return result, err } -func (s *TimerLayerTeamStore) AnalyticsPrivateTeamCount() (int64, error) { +func (s *TimerLayerTeamStore) AnalyticsTeamCount(opts *model.TeamSearch) (int64, error) { start := timemodule.Now() - result, err := s.TeamStore.AnalyticsPrivateTeamCount() - - elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) - if s.Root.Metrics != nil { - success := "false" - if err == nil { - success = "true" - } - s.Root.Metrics.ObserveStoreMethodDuration("TeamStore.AnalyticsPrivateTeamCount", success, elapsed) - } - return result, err -} - -func (s *TimerLayerTeamStore) AnalyticsPublicTeamCount() (int64, error) { - start := timemodule.Now() - - result, err := s.TeamStore.AnalyticsPublicTeamCount() - - elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) - if s.Root.Metrics != nil { - success := "false" - if err == nil { - success = "true" - } - s.Root.Metrics.ObserveStoreMethodDuration("TeamStore.AnalyticsPublicTeamCount", success, elapsed) - } - return result, err -} - -func (s *TimerLayerTeamStore) AnalyticsTeamCount(includeDeleted bool) (int64, error) { - start := timemodule.Now() - - result, err := s.TeamStore.AnalyticsTeamCount(includeDeleted) + result, err := s.TeamStore.AnalyticsTeamCount(opts) elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) if s.Root.Metrics != nil { @@ -6829,10 +7095,10 @@ func (s *TimerLayerTeamStore) GetAllForExportAfter(limit int, afterID string) ([ return result, err } -func (s *TimerLayerTeamStore) GetAllPage(offset int, limit int) ([]*model.Team, error) { +func (s *TimerLayerTeamStore) GetAllPage(offset int, limit int, opts *model.TeamSearch) ([]*model.Team, error) { start := timemodule.Now() - result, err := s.TeamStore.GetAllPage(offset, limit) + result, err := s.TeamStore.GetAllPage(offset, limit, opts) elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) if s.Root.Metrics != nil { @@ -6861,38 +7127,6 @@ func (s *TimerLayerTeamStore) GetAllPrivateTeamListing() ([]*model.Team, error) return result, err } -func (s *TimerLayerTeamStore) GetAllPrivateTeamPageListing(offset int, limit int) ([]*model.Team, error) { - start := timemodule.Now() - - result, err := s.TeamStore.GetAllPrivateTeamPageListing(offset, limit) - - elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) - if s.Root.Metrics != nil { - success := "false" - if err == nil { - success = "true" - } - s.Root.Metrics.ObserveStoreMethodDuration("TeamStore.GetAllPrivateTeamPageListing", success, elapsed) - } - return result, err -} - -func (s *TimerLayerTeamStore) GetAllPublicTeamPageListing(offset int, limit int) ([]*model.Team, error) { - start := timemodule.Now() - - result, err := s.TeamStore.GetAllPublicTeamPageListing(offset, limit) - - elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) - if s.Root.Metrics != nil { - success := "false" - if err == nil { - success = "true" - } - s.Root.Metrics.ObserveStoreMethodDuration("TeamStore.GetAllPublicTeamPageListing", success, elapsed) - } - return result, err -} - func (s *TimerLayerTeamStore) GetAllTeamListing() ([]*model.Team, error) { start := timemodule.Now() @@ -6909,22 +7143,6 @@ func (s *TimerLayerTeamStore) GetAllTeamListing() ([]*model.Team, error) { return result, err } -func (s *TimerLayerTeamStore) GetAllTeamPageListing(offset int, limit int) ([]*model.Team, error) { - start := timemodule.Now() - - result, err := s.TeamStore.GetAllTeamPageListing(offset, limit) - - elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) - if s.Root.Metrics != nil { - success := "false" - if err == nil { - success = "true" - } - s.Root.Metrics.ObserveStoreMethodDuration("TeamStore.GetAllTeamPageListing", success, elapsed) - } - return result, err -} - func (s *TimerLayerTeamStore) GetByInviteId(inviteID string) (*model.Team, error) { start := timemodule.Now() @@ -7356,10 +7574,10 @@ func (s *TimerLayerTeamStore) SaveMultipleMembers(members []*model.TeamMember, m return result, err } -func (s *TimerLayerTeamStore) SearchAll(term string, opts *model.TeamSearch) ([]*model.Team, error) { +func (s *TimerLayerTeamStore) SearchAll(opts *model.TeamSearch) ([]*model.Team, error) { start := timemodule.Now() - result, err := s.TeamStore.SearchAll(term, opts) + result, err := s.TeamStore.SearchAll(opts) elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) if s.Root.Metrics != nil { @@ -7372,10 +7590,10 @@ func (s *TimerLayerTeamStore) SearchAll(term string, opts *model.TeamSearch) ([] return result, err } -func (s *TimerLayerTeamStore) SearchAllPaged(term string, opts *model.TeamSearch) ([]*model.Team, int64, error) { +func (s *TimerLayerTeamStore) SearchAllPaged(opts *model.TeamSearch) ([]*model.Team, int64, error) { start := timemodule.Now() - result, resultVar1, err := s.TeamStore.SearchAllPaged(term, opts) + result, resultVar1, err := s.TeamStore.SearchAllPaged(opts) elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) if s.Root.Metrics != nil { @@ -7388,10 +7606,10 @@ func (s *TimerLayerTeamStore) SearchAllPaged(term string, opts *model.TeamSearch return result, resultVar1, err } -func (s *TimerLayerTeamStore) SearchOpen(term string) ([]*model.Team, error) { +func (s *TimerLayerTeamStore) SearchOpen(opts *model.TeamSearch) ([]*model.Team, error) { start := timemodule.Now() - result, err := s.TeamStore.SearchOpen(term) + result, err := s.TeamStore.SearchOpen(opts) elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) if s.Root.Metrics != nil { @@ -7404,10 +7622,10 @@ func (s *TimerLayerTeamStore) SearchOpen(term string) ([]*model.Team, error) { return result, err } -func (s *TimerLayerTeamStore) SearchPrivate(term string) ([]*model.Team, error) { +func (s *TimerLayerTeamStore) SearchPrivate(opts *model.TeamSearch) ([]*model.Team, error) { start := timemodule.Now() - result, err := s.TeamStore.SearchPrivate(term) + result, err := s.TeamStore.SearchPrivate(opts) elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) if s.Root.Metrics != nil { @@ -9832,6 +10050,7 @@ func New(childStore store.Store, metrics einterfaces.MetricsInterface) *TimerLay newStore.ProductNoticesStore = &TimerLayerProductNoticesStore{ProductNoticesStore: childStore.ProductNotices(), Root: &newStore} newStore.ReactionStore = &TimerLayerReactionStore{ReactionStore: childStore.Reaction(), Root: &newStore} newStore.RemoteClusterStore = &TimerLayerRemoteClusterStore{RemoteClusterStore: childStore.RemoteCluster(), Root: &newStore} + newStore.RetentionPolicyStore = &TimerLayerRetentionPolicyStore{RetentionPolicyStore: childStore.RetentionPolicy(), Root: &newStore} newStore.RoleStore = &TimerLayerRoleStore{RoleStore: childStore.Role(), Root: &newStore} newStore.SchemeStore = &TimerLayerSchemeStore{SchemeStore: childStore.Scheme(), Root: &newStore} newStore.SessionStore = &TimerLayerSessionStore{SessionStore: childStore.Session(), Root: &newStore} diff --git a/web/context.go b/web/context.go index b10da5c7b7..35070db0a5 100644 --- a/web/context.go +++ b/web/context.go @@ -407,6 +407,17 @@ func (c *Context) RequirePostId() *Context { return c } +func (c *Context) RequirePolicyId() *Context { + if c.Err != nil { + return c + } + + if !model.IsValidId(c.Params.PolicyId) { + c.SetInvalidUrlParam("policy_id") + } + return c +} + func (c *Context) RequireAppId() *Context { if c.Err != nil { return c diff --git a/web/params.go b/web/params.go index 8ab509fdff..7f87624f09 100644 --- a/web/params.go +++ b/web/params.go @@ -32,6 +32,7 @@ type Params struct { Timestamp int64 ChannelId string PostId string + PolicyId string FileId string Filename string UploadId string @@ -84,6 +85,7 @@ type Params struct { CategoryId string WarnMetricId string ExportName string + ExcludePolicyConstrained bool // Cloud InvoiceId string @@ -129,6 +131,10 @@ func ParamsFromRequest(r *http.Request) *Params { params.PostId = val } + if val, ok := props["policy_id"]; ok { + params.PolicyId = val + } + if val, ok := props["file_id"]; ok { params.FileId = val } @@ -351,5 +357,9 @@ func ParamsFromRequest(r *http.Request) *Params { params.ExportName = val } + if val, err := strconv.ParseBool(query.Get("exclude_policy_constrained")); err == nil { + params.ExcludePolicyConstrained = val + } + return params }