diff --git a/go.mod b/go.mod index ab2aa367e9..5951c71c05 100644 --- a/go.mod +++ b/go.mod @@ -26,8 +26,8 @@ require ( github.com/jmoiron/sqlx v1.4.0 github.com/lib/pq v1.10.9 github.com/mattermost/mattermost-plugin-playbooks/client v0.7.0 - github.com/mattermost/mattermost/server/public v0.1.8 - github.com/mattermost/mattermost/server/v8 v8.0.0-20241112090719-5eef415a39e1 + github.com/mattermost/mattermost/server/public v0.1.9 + github.com/mattermost/mattermost/server/v8 v8.0.0-20241113102039-053d0b5f0ad5 github.com/mattermost/morph v1.1.0 github.com/mitchellh/mapstructure v1.5.0 github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum index bd306f3ba5..50612c6be3 100644 --- a/go.sum +++ b/go.sum @@ -295,10 +295,10 @@ github.com/mattermost/ldap v0.0.0-20231116144001-0f480c025956 h1:Y1Tu/swM31pVwwb github.com/mattermost/ldap v0.0.0-20231116144001-0f480c025956/go.mod h1:SRl30Lb7/QoYyohYeVBuqYvvmXSZJxZgiV3Zf6VbxjI= github.com/mattermost/logr/v2 v2.0.21 h1:CMHsP+nrbRlEC4g7BwOk1GAnMtHkniFhlSQPXy52be4= github.com/mattermost/logr/v2 v2.0.21/go.mod h1:kZkB/zqKL9e+RY5gB3vGpsyenC+TpuiOenjMkvJJbzc= -github.com/mattermost/mattermost/server/public v0.1.8 h1:Z2PUXR4YGquuSo3ojNUl0aazOMSRqALjyMaf20jNIy4= -github.com/mattermost/mattermost/server/public v0.1.8/go.mod h1:SkTKbMul91Rq0v2dIxe8mqzUOY+3KwlwwLmAlxDfGCk= -github.com/mattermost/mattermost/server/v8 v8.0.0-20241112090719-5eef415a39e1 h1:qNW9//+lx3kwCETTCvqjrFgQ3SH0DyHusE3PnGJfHVE= -github.com/mattermost/mattermost/server/v8 v8.0.0-20241112090719-5eef415a39e1/go.mod h1:B4pQsrbZs6yO4GpWY6nCJPNG7myB0r3gvlFWWlGABmc= +github.com/mattermost/mattermost/server/public v0.1.9 h1:l/OKPRVuFeqL0yqRVC/JpveG5sLNKcT9llxqMkO9e+s= +github.com/mattermost/mattermost/server/public v0.1.9/go.mod h1:SkTKbMul91Rq0v2dIxe8mqzUOY+3KwlwwLmAlxDfGCk= +github.com/mattermost/mattermost/server/v8 v8.0.0-20241113102039-053d0b5f0ad5 h1:XVePV2EZhapQ6JIGFycfTd1QgxICvDs8rRUy3pqxagQ= +github.com/mattermost/mattermost/server/v8 v8.0.0-20241113102039-053d0b5f0ad5/go.mod h1:B4pQsrbZs6yO4GpWY6nCJPNG7myB0r3gvlFWWlGABmc= github.com/mattermost/morph v1.1.0 h1:Q9vrJbeM3s2jfweGheq12EFIzdNp9a/6IovcbvOQ6Cw= github.com/mattermost/morph v1.1.0/go.mod h1:gD+EaqX2UMyyuzmF4PFh4r33XneQ8Nzi+0E8nXjMa3A= github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0 h1:G9tL6JXRBMzjuD1kkBtcnd42kUiT6QDwxfFYu7adM6o= diff --git a/server/app/playbook.go b/server/app/playbook.go index b1c0f04d8c..51bc7842a3 100644 --- a/server/app/playbook.go +++ b/server/app/playbook.go @@ -347,6 +347,9 @@ type PlaybookService interface { // GetPlaybooks retrieves all playbooks GetPlaybooks() ([]Playbook, error) + // GetActivePlaybooks retrieves all active playbooks + GetActivePlaybooks() ([]Playbook, error) + // GetPlaybooksForTeam retrieves all playbooks on the specified team given the provided options GetPlaybooksForTeam(requesterInfo RequesterInfo, teamID string, opts PlaybookFilterOptions) (GetPlaybooksResults, error) @@ -389,6 +392,9 @@ type PlaybookStore interface { // GetPlaybooks retrieves all playbooks GetPlaybooks() ([]Playbook, error) + // GetActivePlaybooks retrieves all active playbooks + GetActivePlaybooks() ([]Playbook, error) + // GetPlaybooksForTeam retrieves all playbooks on the specified team GetPlaybooksForTeam(requesterInfo RequesterInfo, teamID string, opts PlaybookFilterOptions) (GetPlaybooksResults, error) diff --git a/server/app/playbook_service.go b/server/app/playbook_service.go index 10128ec149..ada11d4a43 100644 --- a/server/app/playbook_service.go +++ b/server/app/playbook_service.go @@ -79,6 +79,10 @@ func (s *playbookService) GetPlaybooks() ([]Playbook, error) { return s.store.GetPlaybooks() } +func (s *playbookService) GetActivePlaybooks() ([]Playbook, error) { + return s.store.GetActivePlaybooks() +} + func (s *playbookService) GetPlaybooksForTeam(requesterInfo RequesterInfo, teamID string, opts PlaybookFilterOptions) (GetPlaybooksResults, error) { return s.store.GetPlaybooksForTeam(requesterInfo, teamID, opts) } diff --git a/server/command/command.go b/server/command/command.go index 8c028c6ae1..c45bc0cf22 100644 --- a/server/command/command.go +++ b/server/command/command.go @@ -1154,7 +1154,7 @@ And... yes, of course, we have emojis return } - gotPlaybooks, err := r.playbookService.GetPlaybooks() + gotPlaybooks, err := r.playbookService.GetActivePlaybooks() if err != nil { r.postCommandResponse("There was an error while retrieving all playbooks. Err: " + err.Error()) return diff --git a/server/sqlstore/playbook.go b/server/sqlstore/playbook.go index 4b4fa46d66..4f40bf084a 100644 --- a/server/sqlstore/playbook.go +++ b/server/sqlstore/playbook.go @@ -334,10 +334,50 @@ func (p *playbookStore) Get(id string) (app.Playbook, error) { return playbook, nil } +func selectAllPlaybooks(builder sq.StatementBuilderType) sq.SelectBuilder { + return builder.Select( + "p.ID", + "p.Title", + "p.Description", + "p.TeamID", + "p.Public", + "p.CreatePublicIncident AS CreatePublicPlaybookRun", + "p.CreateAt", + "p.DeleteAt", + "p.NumStages", + "p.NumSteps", + "COUNT(i.ID) AS NumRuns", + "COALESCE(MAX(i.CreateAt), 0) AS LastRunAt", + `( + 1 + -- Channel creation is hard-coded + CASE WHEN p.InviteUsersEnabled THEN 1 ELSE 0 END + + CASE WHEN p.DefaultCommanderEnabled THEN 1 ELSE 0 END + + CASE WHEN p.BroadcastEnabled THEN 1 ELSE 0 END + + CASE WHEN p.WebhookOnCreationEnabled THEN 1 ELSE 0 END + + CASE WHEN p.MessageOnJoinEnabled THEN 1 ELSE 0 END + + CASE WHEN p.WebhookOnStatusUpdateEnabled THEN 1 ELSE 0 END + + CASE WHEN p.SignalAnyKeywordsEnabled THEN 1 ELSE 0 END + + CASE WHEN p.CategorizeChannelEnabled THEN 1 ELSE 0 END + + CASE WHEN p.CreateChannelMemberOnNewParticipant THEN 1 ELSE 0 END + + CASE WHEN p.RemoveChannelMemberOnRemovedParticipant THEN 1 ELSE 0 END + ) AS NumActions`, + "COALESCE(ChannelNameTemplate, '') ChannelNameTemplate", + "COALESCE(s.DefaultPlaybookAdminRole, 'playbook_admin') DefaultPlaybookAdminRole", + "COALESCE(s.DefaultPlaybookMemberRole, 'playbook_member') DefaultPlaybookMemberRole", + "COALESCE(s.DefaultRunAdminRole, 'run_admin') DefaultRunAdminRole", + "COALESCE(s.DefaultRunMemberRole, 'run_member') DefaultRunMemberRole", + ). + From("IR_Playbook AS p"). + LeftJoin("IR_Incident AS i ON p.ID = i.PlaybookID"). + LeftJoin("Teams t ON t.Id = p.TeamID"). + LeftJoin("Schemes s ON t.SchemeId = s.Id"). + GroupBy("p.ID"). + GroupBy("s.Id") +} + // GetPlaybooks retrieves all playbooks that are not deleted. // Members are not retrieved for this as the query would be large and we don't need it for this for now. -// This is only used for the keywords feature -func (p *playbookStore) GetPlaybooks() ([]app.Playbook, error) { +func (p *playbookStore) GetActivePlaybooks() ([]app.Playbook, error) { tx, err := p.store.db.Beginx() if err != nil { return nil, errors.Wrap(err, "could not begin transaction") @@ -345,47 +385,31 @@ func (p *playbookStore) GetPlaybooks() ([]app.Playbook, error) { defer p.store.finalizeTransaction(tx) var playbooks []app.Playbook - err = p.store.selectBuilder(tx, &playbooks, p.store.builder. - Select( - "p.ID", - "p.Title", - "p.Description", - "p.TeamID", - "p.Public", - "p.CreatePublicIncident AS CreatePublicPlaybookRun", - "p.CreateAt", - "p.DeleteAt", - "p.NumStages", - "p.NumSteps", - "COUNT(i.ID) AS NumRuns", - "COALESCE(MAX(i.CreateAt), 0) AS LastRunAt", - `( - 1 + -- Channel creation is hard-coded - CASE WHEN p.InviteUsersEnabled THEN 1 ELSE 0 END + - CASE WHEN p.DefaultCommanderEnabled THEN 1 ELSE 0 END + - CASE WHEN p.BroadcastEnabled THEN 1 ELSE 0 END + - CASE WHEN p.WebhookOnCreationEnabled THEN 1 ELSE 0 END + - CASE WHEN p.MessageOnJoinEnabled THEN 1 ELSE 0 END + - CASE WHEN p.WebhookOnStatusUpdateEnabled THEN 1 ELSE 0 END + - CASE WHEN p.SignalAnyKeywordsEnabled THEN 1 ELSE 0 END + - CASE WHEN p.CategorizeChannelEnabled THEN 1 ELSE 0 END + - CASE WHEN p.CreateChannelMemberOnNewParticipant THEN 1 ELSE 0 END + - CASE WHEN p.RemoveChannelMemberOnRemovedParticipant THEN 1 ELSE 0 END - ) AS NumActions`, - "COALESCE(ChannelNameTemplate, '') ChannelNameTemplate", - "COALESCE(s.DefaultPlaybookAdminRole, 'playbook_admin') DefaultPlaybookAdminRole", - "COALESCE(s.DefaultPlaybookMemberRole, 'playbook_member') DefaultPlaybookMemberRole", - "COALESCE(s.DefaultRunAdminRole, 'run_admin') DefaultRunAdminRole", - "COALESCE(s.DefaultRunMemberRole, 'run_member') DefaultRunMemberRole", - ). - From("IR_Playbook AS p"). - LeftJoin("IR_Incident AS i ON p.ID = i.PlaybookID"). - LeftJoin("Teams t ON t.Id = p.TeamID"). - LeftJoin("Schemes s ON t.SchemeId = s.Id"). - Where(sq.Eq{"p.DeleteAt": 0}). - GroupBy("p.ID"). - GroupBy("s.Id")) + err = p.store.selectBuilder(tx, &playbooks, + selectAllPlaybooks(p.store.builder).Where(sq.Eq{"p.DeleteAt": 0}), + ) + if err == sql.ErrNoRows { + return nil, errors.Wrap(app.ErrNotFound, "no playbooks found") + } else if err != nil { + return nil, errors.Wrap(err, "failed to get playbooks") + } + + return playbooks, nil +} +// GetPlaybooks retrieves all playbooks, even deleted ones. +// Members are not retrieved for this as the query would be large and we don't need it for this for now. +func (p *playbookStore) GetPlaybooks() ([]app.Playbook, error) { + tx, err := p.store.db.Beginx() + if err != nil { + return nil, errors.Wrap(err, "could not begin transaction") + } + defer p.store.finalizeTransaction(tx) + + var playbooks []app.Playbook + err = p.store.selectBuilder(tx, &playbooks, + selectAllPlaybooks(p.store.builder), + ) if err == sql.ErrNoRows { return nil, errors.Wrap(app.ErrNotFound, "no playbooks found") } else if err != nil { diff --git a/server/sqlstore/playbook_test.go b/server/sqlstore/playbook_test.go index 7e91424070..bd2b7d5fc8 100644 --- a/server/sqlstore/playbook_test.go +++ b/server/sqlstore/playbook_test.go @@ -879,7 +879,7 @@ func TestGetPlaybooksForTeam(t *testing.T) { playbookStore := setupPlaybookStore(t, db) t.Run(driverName+" - zero playbooks", func(t *testing.T) { - result, err := playbookStore.GetPlaybooks() + result, err := playbookStore.GetActivePlaybooks() require.NoError(t, err) require.ElementsMatch(t, []app.Playbook{}, result) }) @@ -1343,7 +1343,7 @@ func TestGetPlaybooksForKeywords(t *testing.T) { playbookStore := setupPlaybookStore(t, db) t.Run("zero playbooks", func(t *testing.T) { - result, err := playbookStore.GetPlaybooks() + result, err := playbookStore.GetActivePlaybooks() require.NoError(t, err) require.ElementsMatch(t, []app.Playbook{}, result) }) @@ -1420,7 +1420,7 @@ func TestGetTimeLastUpdated(t *testing.T) { playbookStore := setupPlaybookStore(t, db) t.Run("zero playbooks", func(t *testing.T) { - result, err := playbookStore.GetPlaybooks() + result, err := playbookStore.GetActivePlaybooks() require.NoError(t, err) require.ElementsMatch(t, []app.Playbook{}, result) @@ -1539,7 +1539,7 @@ func TestGetPlaybookIDsForUser(t *testing.T) { playbookStore := setupPlaybookStore(t, db) t.Run("zero playbooks", func(t *testing.T) { - result, err := playbookStore.GetPlaybooks() + result, err := playbookStore.GetActivePlaybooks() require.NoError(t, err) require.ElementsMatch(t, []app.Playbook{}, result) }) diff --git a/server/support_packet.go b/server/support_packet.go index ce2236b857..a877d75777 100644 --- a/server/support_packet.go +++ b/server/support_packet.go @@ -16,6 +16,8 @@ type SupportPacket struct { Version string `yaml:"version"` // The total number of playbooks. TotalPlaybooks int64 `yaml:"total_playbooks"` + // The number of active playbooks. + ActivePlaybooks int64 `yaml:"active_playbooks"` // The total number of playbook runs. TotalPlaybookRuns int64 `yaml:"total_playbook_runs"` } @@ -28,6 +30,11 @@ func (p *Plugin) GenerateSupportData(_ *plugin.Context) ([]*model.FileData, erro result = multierror.Append(result, errors.Wrap(err, "Failed to get total number of playbooks for Support Packet")) } + activePlaybooks, err := p.playbookService.GetActivePlaybooks() + if err != nil { + result = multierror.Append(result, errors.Wrap(err, "Failed to get number of active playbooks for Support Packet")) + } + playbookRuns, err := p.playbookRunService.GetPlaybookRuns(app.RequesterInfo{IsAdmin: true}, app.PlaybookRunFilterOptions{SkipExtras: true}) if err != nil { result = multierror.Append(result, errors.Wrap(err, "Failed to get total number of playbook runs for Support Packet")) @@ -36,6 +43,7 @@ func (p *Plugin) GenerateSupportData(_ *plugin.Context) ([]*model.FileData, erro diagnostics := SupportPacket{ Version: manifest.Version, TotalPlaybooks: int64(len(playbooks)), + ActivePlaybooks: int64(len(activePlaybooks)), TotalPlaybookRuns: int64(playbookRuns.TotalCount), } b, err := yaml.Marshal(diagnostics) diff --git a/server/support_packet_test.go b/server/support_packet_test.go new file mode 100644 index 0000000000..b163c4fc52 --- /dev/null +++ b/server/support_packet_test.go @@ -0,0 +1,39 @@ +package main + +import ( + "archive/zip" + "bytes" + "context" + "path" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" +) + +func TestGenerateSupportData(t *testing.T) { + e := Setup(t) + e.CreateBasic() + + data, _, err := e.ServerAdminClient.GenerateSupportPacket(context.Background()) + require.NoError(t, err) + require.NotEmpty(t, data) + + zr, err := zip.NewReader(bytes.NewReader(data), int64(len(data))) + require.NoError(t, err) + require.NotNil(t, zr) + + f, err := zr.Open(path.Join(manifest.Id, "diagnostics.yaml")) + require.NoError(t, err) + require.NotNil(t, f) + + var sp SupportPacket + err = yaml.NewDecoder(f).Decode(&sp) + require.NoError(t, err) + + assert.Equal(t, manifest.Version, sp.Version) + assert.Equal(t, int64(4), sp.TotalPlaybooks) + assert.Equal(t, int64(3), sp.ActivePlaybooks) + assert.Equal(t, int64(1), sp.TotalPlaybookRuns) +}