Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MM-61124] Add diagnostics data to the Support Packet #1957

Merged
merged 8 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@ require (
github.com/gorilla/mux v1.8.1
github.com/graph-gophers/dataloader/v7 v7.1.0
github.com/graph-gophers/graphql-go v1.4.0
github.com/hashicorp/go-multierror v1.1.1
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
Expand All @@ -36,6 +37,7 @@ require (
github.com/stretchr/testify v1.9.0
github.com/writeas/go-strip-markdown v2.0.1+incompatible
gopkg.in/guregu/null.v4 v4.0.0
gopkg.in/yaml.v3 v3.0.1
)

require (
Expand Down Expand Up @@ -107,7 +109,6 @@ require (
github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-plugin v1.6.1 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
Expand Down Expand Up @@ -203,7 +204,6 @@ require (
gopkg.in/mail.v2 v2.3.1 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/gc/v3 v3.0.0-20240722195230-4a140ff9c08e // indirect
modernc.org/libc v1.55.3 // indirect
modernc.org/mathutil v1.6.0 // indirect
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
10 changes: 5 additions & 5 deletions server/api_runs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1304,7 +1304,7 @@ func TestIgnoreKeywords(t *testing.T) {
},
},
}
botPost, err := e.Srv.Store().Post().Save(botPost)
botPost, err := e.Srv.Store().Post().Save(e.Context, botPost)
require.NoError(t, err)

// Create post action request
Expand All @@ -1321,14 +1321,14 @@ func TestIgnoreKeywords(t *testing.T) {
require.NoError(t, err)

// Make the request
result, err := e.ServerClient.DoAPIRequestBytes("POST", e.ServerClient.URL+"/plugins/"+manifest.Id+"/api/v0/signal/keywords/ignore-thread", reqBytes, "")
result, err := e.ServerClient.DoAPIRequestBytes(context.Background(), "POST", e.ServerClient.URL+"/plugins/"+manifest.Id+"/api/v0/signal/keywords/ignore-thread", reqBytes, "")
require.Error(t, err)
require.Equal(t, http.StatusForbidden, result.StatusCode)
})

t.Run("has permission to channel", func(t *testing.T) {
// Add user to private channel
_, _, err := e.ServerAdminClient.AddChannelMember(e.BasicPrivateChannel.Id, e.RegularUser.Id)
_, _, err := e.ServerAdminClient.AddChannelMember(context.Background(), e.BasicPrivateChannel.Id, e.RegularUser.Id)
require.NoError(t, err)

// Create a bot post in the private channel
Expand All @@ -1348,7 +1348,7 @@ func TestIgnoreKeywords(t *testing.T) {
},
},
}
botPost, err = e.Srv.Store().Post().Save(botPost)
botPost, err = e.Srv.Store().Post().Save(e.Context, botPost)
require.NoError(t, err)

// Create post action request
Expand All @@ -1365,7 +1365,7 @@ func TestIgnoreKeywords(t *testing.T) {
require.NoError(t, err)

// Make the request
result, err := e.ServerClient.DoAPIRequestBytes("POST", e.ServerClient.URL+"/plugins/"+manifest.Id+"/api/v0/signal/keywords/ignore-thread", reqBytes, "")
result, err := e.ServerClient.DoAPIRequestBytes(context.Background(), "POST", e.ServerClient.URL+"/plugins/"+manifest.Id+"/api/v0/signal/keywords/ignore-thread", reqBytes, "")
require.NoError(t, err)
require.Equal(t, http.StatusOK, result.StatusCode)
})
Expand Down
6 changes: 6 additions & 0 deletions server/app/playbook.go
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,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)

Expand Down Expand Up @@ -388,6 +391,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)

Expand Down
4 changes: 4 additions & 0 deletions server/app/playbook_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,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)
}
Expand Down
2 changes: 1 addition & 1 deletion server/command/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -1155,7 +1155,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
Expand Down
108 changes: 66 additions & 42 deletions server/sqlstore/playbook.go
Original file line number Diff line number Diff line change
Expand Up @@ -336,58 +336,82 @@ 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")
}
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 {
Expand Down
8 changes: 4 additions & 4 deletions server/sqlstore/playbook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -880,7 +880,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)
})
Expand Down Expand Up @@ -1344,7 +1344,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)
})
Expand Down Expand Up @@ -1421,7 +1421,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)

Expand Down Expand Up @@ -1540,7 +1540,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)
})
Expand Down
58 changes: 58 additions & 0 deletions server/support_packet.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package main

import (
"path/filepath"

"github.com/hashicorp/go-multierror"
"github.com/pkg/errors"
"gopkg.in/yaml.v3"

"github.com/mattermost/mattermost-plugin-playbooks/server/app"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/plugin"
)

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"`
}

func (p *Plugin) GenerateSupportData(_ *plugin.Context) ([]*model.FileData, error) {
var result *multierror.Error

playbooks, err := p.playbookService.GetPlaybooks()
if err != nil {
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"))
}

diagnostics := SupportPacket{
Version: manifest.Version,
TotalPlaybooks: int64(len(playbooks)),
ActivePlaybooks: int64(len(activePlaybooks)),
TotalPlaybookRuns: int64(playbookRuns.TotalCount),
}
body, err := yaml.Marshal(diagnostics)
if err != nil {
return nil, errors.Wrap(err, "Failed to marshal diagnostics")
}

return []*model.FileData{{
Filename: filepath.Join(manifest.Id, "diagnostics.yaml"),
Body: body,
}}, result.ErrorOrNil()
}
39 changes: 39 additions & 0 deletions server/support_packet_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
Loading