package plugin import ( "fmt" "strings" "github.com/mattermost/mattermost/server/public/model" ) func (p *Plugin) commandLink(args *model.CommandArgs, split []string) (*model.CommandResponse, *model.AppError) { if len(split) < 3 { return &model.CommandResponse{ ResponseType: model.CommandResponseTypeEphemeral, Text: "❌ Usage: /sentry link ", }, nil } projectSlug := split[2] channelID := args.ChannelId hooks := HookSettings{ EventAlert: true, MetricAlert: true, Issue: true, Comment: true, } project, err := p.linkProjectToChannel(projectSlug, channelID, hooks) if err != nil { return &model.CommandResponse{ ResponseType: model.CommandResponseTypeEphemeral, Text: "❌ " + err.Error(), }, nil } return &model.CommandResponse{ ResponseType: model.CommandResponseTypeEphemeral, Text: fmt.Sprintf( "✅ Linked **%s** (`%s`) to this channel", project.Name, project.Slug, ), }, nil } func (p *Plugin) commandUnlink(args *model.CommandArgs, split []string) (*model.CommandResponse, *model.AppError) { if len(split) < 3 { return &model.CommandResponse{ ResponseType: model.CommandResponseTypeEphemeral, Text: "❌ Usage: /sentry unlink ", }, nil } slug := split[2] projects, err := p.getAllProjects() if err != nil { return &model.CommandResponse{ ResponseType: model.CommandResponseTypeEphemeral, Text: "ℹ️ No linked projects", }, nil } var removed *LinkedProject for _, project := range projects { if project.Slug == slug { removed = project if err := p.deleteProject(project.ID); err != nil { return &model.CommandResponse{ ResponseType: model.CommandResponseTypeEphemeral, Text: "❌ Failed to unlink project: " + err.Error(), }, nil } break } } if removed == nil { return &model.CommandResponse{ ResponseType: model.CommandResponseTypeEphemeral, Text: "❌ Project not found", }, nil } return &model.CommandResponse{ ResponseType: model.CommandResponseTypeEphemeral, Text: fmt.Sprintf( "✅ Unlinked **%s** (`%s`)", removed.Name, removed.Slug, ), }, nil } func (p *Plugin) commandList(args *model.CommandArgs) (*model.CommandResponse, *model.AppError) { projects, err := p.getAllProjects() if err != nil || len(projects) == 0 { return &model.CommandResponse{ ResponseType: model.CommandResponseTypeEphemeral, Text: "_No linked Sentry projects_", }, nil } var lines []string for _, project := range projects { channelName := project.ChannelID if ch, err := p.API.GetChannel(project.ChannelID); err == nil { channelName = "~" + ch.Name } lines = append( lines, fmt.Sprintf( "• **%s** (`%s`) → %s", project.Name, project.Slug, channelName, ), ) } return &model.CommandResponse{ ResponseType: model.CommandResponseTypeEphemeral, Text: "🔗 **Linked Sentry projects:**\n\n" + strings.Join(lines, "\n"), }, nil } func (p *Plugin) commandHelp() *model.CommandResponse { return &model.CommandResponse{ ResponseType: model.CommandResponseTypeEphemeral, Text: `**Sentry plugin commands** • /sentry link — link project to channel • /sentry help — show help`, } } func (p *Plugin) commandSetup(args *model.CommandArgs) (*model.CommandResponse, *model.AppError) { if args.TriggerId == "" { return &model.CommandResponse{ ResponseType: model.CommandResponseTypeEphemeral, Text: "This command must be run from Mattermost UI", }, nil } // Получаем каналы пользователя channels, appErr := p.API.GetChannelsForTeamForUser(args.TeamId, args.UserId, false) if appErr != nil { return &model.CommandResponse{ ResponseType: model.CommandResponseTypeEphemeral, Text: "❌ Failed to load channels", }, nil } options := []*model.PostActionOptions{} for _, ch := range channels { options = append(options, &model.PostActionOptions{ Text: ch.DisplayName, Value: ch.Id, }) } modal := &model.Dialog{ Title: "Sentry Setup", CallbackId: "sentry_setup", SubmitLabel: "Save", Elements: []model.DialogElement{ // ───── General ───── { DisplayName: "Default channel", Name: "default_channel_id", Type: "select", Options: options, Default: args.ChannelId, }, { DisplayName: "Sentry project slug", Name: "project_slug", Type: "text", Placeholder: "frontend-app", }, // ───── Webhook types ───── {DisplayName: "Event alerts", Name: "hook_event_alert", Type: "bool", Default: "true"}, {DisplayName: "Metric alerts", Name: "hook_metric_alert", Type: "bool", Default: "true"}, {DisplayName: "Issues", Name: "hook_issue", Type: "bool", Default: "true"}, {DisplayName: "Comments", Name: "hook_comment", Type: "bool", Default: "true"}, }, } req := model.OpenDialogRequest{ TriggerId: args.TriggerId, URL: "/plugins/ru.loop.plugin.sentry/dialog/submit", Dialog: *modal, } if appErr := p.API.OpenInteractiveDialog(req); appErr != nil { return &model.CommandResponse{ ResponseType: model.CommandResponseTypeEphemeral, Text: "❌ Failed to open setup dialog: " + appErr.Error(), }, nil } return &model.CommandResponse{}, nil } func (p *Plugin) commandSetupHook( args *model.CommandArgs, hook string, ) (*model.CommandResponse, *model.AppError) { project, err := p.getProjectByChannel(args.ChannelId) if err != nil { return ephemeral("❌ " + err.Error()), nil } hookType := HookType(hook) if !isHookEnabled(project, hookType) { return ephemeral("⚠️ This webhook is disabled in setup"), nil } switch hookType { case HookEventAlert: return p.openEventAlertSetup(args, project) case HookMetricAlert: return p.openMetricAlertSetup(args, project) case HookIssue: return p.openIssueSetup(args, project) case HookComment: return p.openCommentSetup(args, project) default: return ephemeral("❌ Unknown webhook type"), nil } }