307 lines
9.3 KiB
Go
307 lines
9.3 KiB
Go
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 {
|
||
post := &model.Post{Message: "❌ Usage: /sentry link <project_slug>"}
|
||
_ = p.sendDMToUser(args.UserId, post)
|
||
return &model.CommandResponse{}, 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 {
|
||
post := &model.Post{Message: "❌ " + err.Error()}
|
||
_ = p.sendDMToUser(args.UserId, post)
|
||
return &model.CommandResponse{}, nil
|
||
}
|
||
|
||
post := &model.Post{
|
||
Message: fmt.Sprintf(
|
||
"✅ Linked **%s** (`%s`) to this channel",
|
||
project.Name,
|
||
project.Slug,
|
||
),
|
||
}
|
||
_ = p.sendDMToUser(args.UserId, post)
|
||
return &model.CommandResponse{}, nil
|
||
}
|
||
|
||
func (p *Plugin) commandUnlink(args *model.CommandArgs, split []string) (*model.CommandResponse, *model.AppError) {
|
||
if len(split) < 3 {
|
||
post := &model.Post{Message: "❌ Usage: /sentry unlink <project_slug>"}
|
||
_ = p.sendDMToUser(args.UserId, post)
|
||
return &model.CommandResponse{}, nil
|
||
}
|
||
|
||
slug := split[2]
|
||
|
||
projects, err := p.getAllProjects()
|
||
if err != nil {
|
||
post := &model.Post{Message: "ℹ️ No linked projects"}
|
||
_ = p.sendDMToUser(args.UserId, post)
|
||
return &model.CommandResponse{}, nil
|
||
}
|
||
|
||
var removed *LinkedProject
|
||
for _, project := range projects {
|
||
if project.Slug == slug {
|
||
removed = project
|
||
if err := p.deleteProject(project.ID); err != nil {
|
||
post := &model.Post{Message: "❌ Failed to unlink project: " + err.Error()}
|
||
_ = p.sendDMToUser(args.UserId, post)
|
||
return &model.CommandResponse{}, nil
|
||
}
|
||
break
|
||
}
|
||
}
|
||
|
||
if removed == nil {
|
||
post := &model.Post{Message: "❌ Project not found"}
|
||
_ = p.sendDMToUser(args.UserId, post)
|
||
return &model.CommandResponse{}, nil
|
||
}
|
||
|
||
post := &model.Post{
|
||
Message: fmt.Sprintf(
|
||
"✅ Unlinked **%s** (`%s`)",
|
||
removed.Name,
|
||
removed.Slug,
|
||
),
|
||
}
|
||
_ = p.sendDMToUser(args.UserId, post)
|
||
return &model.CommandResponse{}, nil
|
||
}
|
||
|
||
func (p *Plugin) commandList(args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
|
||
projects, err := p.getAllProjects()
|
||
if err != nil || len(projects) == 0 {
|
||
post := &model.Post{Message: "_No linked Sentry projects_"}
|
||
_ = p.sendDMToUser(args.UserId, post)
|
||
return &model.CommandResponse{}, nil
|
||
}
|
||
|
||
// Формируем таблицу
|
||
var tableRows []string
|
||
|
||
// Заголовок таблицы
|
||
tableRows = append(tableRows, "| Project | Slug | Channel | Hooks |")
|
||
tableRows = append(tableRows, "|---------|------|---------|-------|")
|
||
|
||
// Строки с данными
|
||
for _, project := range projects {
|
||
channelName := project.ChannelID
|
||
if ch, err := p.API.GetChannel(project.ChannelID); err == nil {
|
||
channelName = "~" + ch.Name
|
||
}
|
||
|
||
// Формируем список включенных хуков
|
||
var hooks []string
|
||
if project.Hooks.EventAlert {
|
||
hooks = append(hooks, "Event")
|
||
}
|
||
if project.Hooks.MetricAlert {
|
||
hooks = append(hooks, "Metric")
|
||
}
|
||
if project.Hooks.Issue {
|
||
hooks = append(hooks, "Issue")
|
||
}
|
||
if project.Hooks.Comment {
|
||
hooks = append(hooks, "Comment")
|
||
}
|
||
|
||
hooksStr := strings.Join(hooks, ", ")
|
||
if hooksStr == "" {
|
||
hooksStr = "-"
|
||
}
|
||
|
||
// Экранируем символы для markdown таблицы
|
||
projectName := strings.ReplaceAll(project.Name, "|", "\\|")
|
||
slug := strings.ReplaceAll(project.Slug, "|", "\\|")
|
||
channelName = strings.ReplaceAll(channelName, "|", "\\|")
|
||
hooksStr = strings.ReplaceAll(hooksStr, "|", "\\|")
|
||
|
||
tableRows = append(
|
||
tableRows,
|
||
fmt.Sprintf("| %s | `%s` | %s | %s |", projectName, slug, channelName, hooksStr),
|
||
)
|
||
}
|
||
|
||
message := "🔗 **Linked Sentry projects:**\n\n" + strings.Join(tableRows, "\n")
|
||
post := &model.Post{Message: message}
|
||
_ = p.sendDMToUser(args.UserId, post)
|
||
return &model.CommandResponse{}, nil
|
||
}
|
||
|
||
func (p *Plugin) commandHelp() *model.CommandResponse {
|
||
// Help отправляется через ephemeral в ExecuteCommand, но мы не можем получить userId здесь
|
||
// Поэтому оставляем как есть, но это будет обработано в ExecuteCommand
|
||
return &model.CommandResponse{
|
||
ResponseType: model.CommandResponseTypeEphemeral,
|
||
Text: `**Sentry plugin commands**
|
||
|
||
• /sentry link <project_slug> — link project to channel
|
||
• /sentry help — show help`,
|
||
}
|
||
}
|
||
|
||
func (p *Plugin) commandSetup(args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
|
||
p.API.LogDebug("commandSetup called", "triggerId", args.TriggerId)
|
||
|
||
if args.TriggerId == "" {
|
||
p.API.LogWarn("commandSetup: triggerId is empty")
|
||
post := &model.Post{Message: "This command must be run from Loop UI"}
|
||
_ = p.sendDMToUser(args.UserId, post)
|
||
return &model.CommandResponse{}, nil
|
||
}
|
||
|
||
// Получаем каналы пользователя
|
||
channels, appErr := p.API.GetChannelsForTeamForUser(args.TeamId, args.UserId, false)
|
||
if appErr != nil {
|
||
p.API.LogError("Failed to load channels", "error", appErr.Error())
|
||
post := &model.Post{Message: "❌ Failed to load channels"}
|
||
_ = p.sendDMToUser(args.UserId, post)
|
||
return &model.CommandResponse{}, nil
|
||
}
|
||
|
||
channelOptions := []*model.PostActionOptions{}
|
||
for _, ch := range channels {
|
||
channelOptions = append(channelOptions, &model.PostActionOptions{
|
||
Text: ch.DisplayName,
|
||
Value: ch.Id,
|
||
})
|
||
}
|
||
|
||
// Получаем проекты из Sentry
|
||
sentryProjects, err := p.fetchSentryProjects()
|
||
var projectElement model.DialogElement
|
||
|
||
if err != nil {
|
||
// Если не удалось получить проекты, используем текстовое поле как fallback
|
||
p.API.LogWarn("Failed to load Sentry projects, using text input", "error", err.Error())
|
||
projectElement = model.DialogElement{
|
||
DisplayName: "Sentry project slug",
|
||
Name: "project_slug",
|
||
Type: "text",
|
||
Placeholder: "frontend-app",
|
||
}
|
||
} else if len(sentryProjects) == 0 {
|
||
// Если список проектов пустой, используем текстовое поле
|
||
p.API.LogWarn("No Sentry projects found, using text input")
|
||
projectElement = model.DialogElement{
|
||
DisplayName: "Sentry project slug",
|
||
Name: "project_slug",
|
||
Type: "text",
|
||
Placeholder: "frontend-app",
|
||
}
|
||
} else {
|
||
// Формируем опции для select проектов
|
||
projectOptions := []*model.PostActionOptions{}
|
||
for _, proj := range sentryProjects {
|
||
projectOptions = append(projectOptions, &model.PostActionOptions{
|
||
Text: fmt.Sprintf("%s (%s)", proj.Name, proj.Slug),
|
||
Value: proj.Slug,
|
||
})
|
||
}
|
||
projectElement = model.DialogElement{
|
||
DisplayName: "Sentry project",
|
||
Name: "project_slug",
|
||
Type: "select",
|
||
Options: projectOptions,
|
||
}
|
||
p.API.LogDebug("Loaded Sentry projects", "count", len(sentryProjects))
|
||
}
|
||
|
||
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: channelOptions,
|
||
Default: args.ChannelId,
|
||
},
|
||
projectElement,
|
||
|
||
// ───── 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"},
|
||
},
|
||
}
|
||
|
||
p.API.LogDebug("Opening dialog", "elements_count", len(modal.Elements))
|
||
|
||
req := model.OpenDialogRequest{
|
||
TriggerId: args.TriggerId,
|
||
URL: "/plugins/ru.loop.plugin.sentry/dialog/submit",
|
||
Dialog: *modal,
|
||
}
|
||
|
||
if appErr := p.API.OpenInteractiveDialog(req); appErr != nil {
|
||
p.API.LogError("Failed to open setup dialog", "error", appErr.Error())
|
||
return &model.CommandResponse{
|
||
ResponseType: model.CommandResponseTypeEphemeral,
|
||
Text: "❌ Failed to open setup dialog: " + appErr.Error(),
|
||
}, nil
|
||
}
|
||
|
||
p.API.LogDebug("Dialog opened successfully")
|
||
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 {
|
||
post := &model.Post{Message: "❌ " + err.Error()}
|
||
_ = p.sendDMToUser(args.UserId, post)
|
||
return &model.CommandResponse{}, nil
|
||
}
|
||
|
||
hookType := HookType(hook)
|
||
|
||
if !isHookEnabled(project, hookType) {
|
||
post := &model.Post{Message: "⚠️ This webhook is disabled in setup"}
|
||
_ = p.sendDMToUser(args.UserId, post)
|
||
return &model.CommandResponse{}, 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:
|
||
post := &model.Post{Message: "❌ Unknown webhook type"}
|
||
_ = p.sendDMToUser(args.UserId, post)
|
||
return &model.CommandResponse{}, nil
|
||
}
|
||
}
|