Compare commits
14 Commits
2911b2f884
...
02331752fe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02331752fe | ||
|
|
0d3910829d | ||
|
|
8f288cf12e | ||
|
|
1b148092b9 | ||
|
|
24628f4ebb | ||
|
|
c51e580e09 | ||
|
|
82fc7526d0 | ||
|
|
02fecb7eb2 | ||
|
|
6ba23e61c3 | ||
|
|
8883856a63 | ||
|
|
634255725a | ||
|
|
233ba8ae2b | ||
|
|
ccee4a49da | ||
|
|
4080af952d |
26
config.go
26
config.go
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
@@ -8,6 +9,7 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
@@ -16,13 +18,17 @@ type Config struct {
|
|||||||
InitializeSlack bool
|
InitializeSlack bool
|
||||||
SlackToken string
|
SlackToken string
|
||||||
SlackChannels string
|
SlackChannels string
|
||||||
|
PostgresConn string
|
||||||
|
storage Storage
|
||||||
|
queue Queue
|
||||||
|
driver Driver
|
||||||
}
|
}
|
||||||
|
|
||||||
func newConfig() (Config, error) {
|
func newConfig(ctx context.Context) (Config, error) {
|
||||||
return newConfigFromEnv(os.Getenv)
|
return newConfigFromEnv(ctx, os.Getenv)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newConfigFromEnv(getEnv func(string) string) (Config, error) {
|
func newConfigFromEnv(ctx context.Context, getEnv func(string) string) (Config, error) {
|
||||||
def := Config{
|
def := Config{
|
||||||
Port: 8080,
|
Port: 8080,
|
||||||
}
|
}
|
||||||
@@ -73,5 +79,19 @@ func newConfigFromEnv(getEnv func(string) string) (Config, error) {
|
|||||||
} else if err := json.Unmarshal(b, &result); err != nil {
|
} else if err := json.Unmarshal(b, &result); err != nil {
|
||||||
return Config{}, err
|
return Config{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result.driver = NewRAM()
|
||||||
|
if result.PostgresConn != "" {
|
||||||
|
ctx, can := context.WithTimeout(ctx, time.Second*10)
|
||||||
|
defer can()
|
||||||
|
pg, err := NewPostgres(ctx, result.PostgresConn)
|
||||||
|
if err != nil {
|
||||||
|
return Config{}, err
|
||||||
|
}
|
||||||
|
result.driver = pg
|
||||||
|
}
|
||||||
|
result.storage = NewStorage(result.driver)
|
||||||
|
result.queue = NewQueue(result.driver)
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
func TestNewConfig(t *testing.T) {
|
func TestNewConfig(t *testing.T) {
|
||||||
if got, err := newConfigFromEnv(func(k string) string {
|
if got, err := newConfigFromEnv(context.Background(), func(k string) string {
|
||||||
t.Logf("getenv(%s)", k)
|
t.Logf("getenv(%s)", k)
|
||||||
switch k {
|
switch k {
|
||||||
case "PORT":
|
case "PORT":
|
||||||
|
|||||||
195
driver.go
195
driver.go
@@ -2,11 +2,17 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"path"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.etcd.io/bbolt"
|
"go.etcd.io/bbolt"
|
||||||
|
|
||||||
|
_ "github.com/lib/pq"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Driver interface {
|
type Driver interface {
|
||||||
@@ -16,16 +22,197 @@ type Driver interface {
|
|||||||
Set(context.Context, string, string, []byte) error
|
Set(context.Context, string, string, []byte) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Postgres struct {
|
||||||
|
db *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPostgres(ctx context.Context, conn string) (Postgres, error) {
|
||||||
|
db, err := sql.Open("postgres", conn)
|
||||||
|
if err != nil {
|
||||||
|
return Postgres{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pg := Postgres{db: db}
|
||||||
|
if err := pg.setup(ctx); err != nil {
|
||||||
|
pg.Close()
|
||||||
|
return Postgres{}, fmt.Errorf("failed setup: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pg Postgres) setup(ctx context.Context) error {
|
||||||
|
tableQ, err := pg.table("q")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tableM, err := pg.table("m")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := pg.db.ExecContext(ctx, fmt.Sprintf(`
|
||||||
|
CREATE TABLE IF NOT EXISTS %s (
|
||||||
|
id TEXT NOT NULL,
|
||||||
|
v JSONB NOT NULL
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS %s (
|
||||||
|
id TEXT NOT NULL,
|
||||||
|
v JSONB NOT NULL
|
||||||
|
);
|
||||||
|
ALTER TABLE %s DROP CONSTRAINT IF EXISTS %s_id_unique;
|
||||||
|
ALTER TABLE %s ADD CONSTRAINT %s_id_unique UNIQUE (id);
|
||||||
|
ALTER TABLE %s DROP CONSTRAINT IF EXISTS %s_id_unique;
|
||||||
|
ALTER TABLE %s ADD CONSTRAINT %s_id_unique UNIQUE (id);
|
||||||
|
`, tableQ,
|
||||||
|
tableM,
|
||||||
|
tableQ, tableQ,
|
||||||
|
tableQ, tableQ,
|
||||||
|
tableM, tableM,
|
||||||
|
tableM, tableM,
|
||||||
|
)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pg Postgres) table(s string) (string, error) {
|
||||||
|
switch s {
|
||||||
|
case "q":
|
||||||
|
return "spoc_bot_vr_q", nil
|
||||||
|
case "m":
|
||||||
|
return "spoc_bot_vr_messages", nil
|
||||||
|
}
|
||||||
|
return "", errors.New("invalid table " + s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pg Postgres) Close() error {
|
||||||
|
return pg.db.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pg Postgres) ForEach(ctx context.Context, ns string, cb func(string, []byte) error) error {
|
||||||
|
table, err := pg.table(ns)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := pg.db.QueryContext(ctx, fmt.Sprintf(`SELECT id, v FROM %s;`, table))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var id string
|
||||||
|
var v []byte
|
||||||
|
if err := rows.Scan(&id, &v); err != nil {
|
||||||
|
return err
|
||||||
|
} else if err := cb(id, v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pg Postgres) Get(ctx context.Context, ns, id string) ([]byte, error) {
|
||||||
|
table, err := pg.table(ns)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
row := pg.db.QueryRowContext(ctx, fmt.Sprintf(`SELECT v FROM %s WHERE id='%s';`, table, id))
|
||||||
|
if err := row.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var v []byte
|
||||||
|
if err := row.Scan(&v); err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pg Postgres) Set(ctx context.Context, ns, id string, v []byte) error {
|
||||||
|
table, err := pg.table(ns)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if v == nil {
|
||||||
|
_, err = pg.db.ExecContext(ctx, fmt.Sprintf(`DELETE FROM %s WHERE id='%s';`, table, id))
|
||||||
|
return err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = pg.db.ExecContext(ctx, fmt.Sprintf(`INSERT INTO %s (id, v) VALUES ('%s', '%s') ON CONFLICT (id) DO UPDATE SET v = '%s'`, table, id, v, v))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type RAM struct {
|
||||||
|
m map[string]map[string][]byte
|
||||||
|
lock *sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRAM() RAM {
|
||||||
|
return RAM{
|
||||||
|
m: make(map[string]map[string][]byte),
|
||||||
|
lock: &sync.RWMutex{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ram RAM) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ram RAM) ForEach(ctx context.Context, ns string, cb func(string, []byte) error) error {
|
||||||
|
ram.lock.RLock()
|
||||||
|
defer ram.lock.RUnlock()
|
||||||
|
|
||||||
|
for k, v := range ram.m[ns] {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err := cb(k, v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ram RAM) Get(_ context.Context, ns, id string) ([]byte, error) {
|
||||||
|
ram.lock.RLock()
|
||||||
|
defer ram.lock.RUnlock()
|
||||||
|
|
||||||
|
if _, ok := ram.m[ns]; !ok {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return ram.m[ns][id], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ram RAM) Set(_ context.Context, ns, id string, v []byte) error {
|
||||||
|
ram.lock.Lock()
|
||||||
|
defer ram.lock.Unlock()
|
||||||
|
|
||||||
|
if _, ok := ram.m[ns]; !ok {
|
||||||
|
ram.m[ns] = map[string][]byte{}
|
||||||
|
}
|
||||||
|
ram.m[ns][id] = v
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type BBolt struct {
|
type BBolt struct {
|
||||||
db *bbolt.DB
|
db *bbolt.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTestDB() BBolt {
|
func NewTestDBIn(d string) BBolt {
|
||||||
d, err := ioutil.TempDir(os.TempDir(), "test-db-*")
|
d, err := ioutil.TempDir(d, "test-db-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
db, err := NewDB(d)
|
db, err := NewDB(path.Join(d, "bb"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|||||||
84
driver_test.go
Normal file
84
driver_test.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPostgres(t *testing.T) {
|
||||||
|
ctx, can := context.WithTimeout(context.Background(), time.Second*15)
|
||||||
|
defer can()
|
||||||
|
|
||||||
|
conn := os.Getenv("INTEGRATION_POSTGRES_CONN")
|
||||||
|
if conn == "" {
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
pg, err := NewPostgres(ctx, conn)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
testDriver(t, pg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDriverRAM(t *testing.T) {
|
||||||
|
testDriver(t, NewRAM())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDriverBBolt(t *testing.T) {
|
||||||
|
testDriver(t, NewTestDBIn(t.TempDir()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDriver(t *testing.T, d Driver) {
|
||||||
|
ctx, can := context.WithTimeout(context.Background(), time.Second*15)
|
||||||
|
defer can()
|
||||||
|
|
||||||
|
defer d.Close()
|
||||||
|
|
||||||
|
if b, err := d.Get(ctx, "m", "id"); err != nil {
|
||||||
|
t.Error("cannot get from empty:", err)
|
||||||
|
} else if b != nil {
|
||||||
|
t.Error("got fake from empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := d.ForEach(ctx, "m", func(string, []byte) error {
|
||||||
|
return errors.New("should have no hits")
|
||||||
|
}); err != nil {
|
||||||
|
t.Error("failed to forEach empty:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := d.Set(ctx, "m", "id", []byte(`"hello world"`)); err != nil {
|
||||||
|
t.Error("cannot set from empty:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b, err := d.Get(ctx, "m", "id"); err != nil {
|
||||||
|
t.Error("cannot get from full:", err)
|
||||||
|
} else if string(b) != `"hello world"` {
|
||||||
|
t.Error("got fake from full")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := d.ForEach(ctx, "m", func(id string, v []byte) error {
|
||||||
|
if id != "id" {
|
||||||
|
t.Error("for each id weird:", id)
|
||||||
|
}
|
||||||
|
if string(v) != `"hello world"` {
|
||||||
|
t.Error("for each value weird:", string(v))
|
||||||
|
}
|
||||||
|
return io.EOF
|
||||||
|
}); err != io.EOF {
|
||||||
|
t.Error("failed to forEach full:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := d.Set(ctx, "m", "id", nil); err != nil {
|
||||||
|
t.Error("cannot set from full:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b, err := d.Get(ctx, "m", "id"); err != nil {
|
||||||
|
t.Error("cannot get from deleted:", err)
|
||||||
|
} else if b != nil {
|
||||||
|
t.Error("got fake from deleted")
|
||||||
|
}
|
||||||
|
}
|
||||||
5
go.mod
5
go.mod
@@ -7,4 +7,7 @@ require (
|
|||||||
go.etcd.io/bbolt v1.3.9
|
go.etcd.io/bbolt v1.3.9
|
||||||
)
|
)
|
||||||
|
|
||||||
require golang.org/x/sys v0.4.0 // indirect
|
require (
|
||||||
|
github.com/lib/pq v1.10.9 // indirect
|
||||||
|
golang.org/x/sys v0.4.0 // indirect
|
||||||
|
)
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -1,5 +1,7 @@
|
|||||||
github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
|
github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
|
||||||
github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||||
|
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI=
|
go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI=
|
||||||
go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE=
|
go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE=
|
||||||
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
||||||
|
|||||||
15
main.go
15
main.go
@@ -19,10 +19,11 @@ func main() {
|
|||||||
ctx, can := signal.NotifyContext(context.Background(), syscall.SIGINT)
|
ctx, can := signal.NotifyContext(context.Background(), syscall.SIGINT)
|
||||||
defer can()
|
defer can()
|
||||||
|
|
||||||
cfg, err := newConfig()
|
cfg, err := newConfig(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
defer cfg.driver.Close()
|
||||||
|
|
||||||
if err := run(ctx, cfg); err != nil && ctx.Err() == nil {
|
if err := run(ctx, cfg); err != nil && ctx.Err() == nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -115,7 +116,15 @@ func _newHandlerPostAPIV1EventsSlack(cfg Config) http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("slack event: %s", b)
|
m, err := ParseSlack(b)
|
||||||
http.Error(w, "not impl", http.StatusNotImplemented)
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cfg.storage.Upsert(r.Context(), m); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
98
message.go
98
message.go
@@ -1,6 +1,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "encoding/json"
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
type Message struct {
|
type Message struct {
|
||||||
ID string
|
ID string
|
||||||
@@ -39,3 +44,94 @@ func Deserialize(b []byte) (Message, error) {
|
|||||||
err := json.Unmarshal(b, &m)
|
err := json.Unmarshal(b, &m)
|
||||||
return m, err
|
return m, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
slackMessage struct {
|
||||||
|
TS uint64 `json:"event_time"`
|
||||||
|
Event slackEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
slackEvent struct {
|
||||||
|
ID string `json:"event_ts"`
|
||||||
|
Channel string
|
||||||
|
// human
|
||||||
|
ParentID string `json:"thread_ts"`
|
||||||
|
Text string
|
||||||
|
Blocks []slackBlock
|
||||||
|
// bot
|
||||||
|
Bot slackBot `json:"bot_profile"`
|
||||||
|
Attachments []slackAttachment
|
||||||
|
}
|
||||||
|
|
||||||
|
slackBlock struct {
|
||||||
|
Elements []slackElement
|
||||||
|
}
|
||||||
|
|
||||||
|
slackElement struct {
|
||||||
|
Elements []slackElement
|
||||||
|
RichText string `json:"text"`
|
||||||
|
}
|
||||||
|
|
||||||
|
slackBot struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
slackAttachment struct {
|
||||||
|
Color string
|
||||||
|
Title string
|
||||||
|
Text string
|
||||||
|
Fields []slackField
|
||||||
|
Actions []slackAction
|
||||||
|
}
|
||||||
|
|
||||||
|
slackField struct {
|
||||||
|
Value string
|
||||||
|
Title string
|
||||||
|
}
|
||||||
|
|
||||||
|
slackAction struct{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func ParseSlack(b []byte) (Message, error) {
|
||||||
|
s, err := parseSlack(b)
|
||||||
|
if err != nil {
|
||||||
|
return Message{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Event.Bot.Name != "" {
|
||||||
|
if len(s.Event.Attachments) == 0 {
|
||||||
|
return Message{}, errors.New("bot message has no attachments")
|
||||||
|
} else if !strings.Contains(s.Event.Attachments[0].Title, ": Firing: ") {
|
||||||
|
return Message{}, errors.New("bot message attachment is not Firing")
|
||||||
|
}
|
||||||
|
return Message{
|
||||||
|
ID: fmt.Sprintf("%s/%v", s.Event.ID, s.TS),
|
||||||
|
TS: s.TS,
|
||||||
|
Source: fmt.Sprintf(`https://renderinc.slack.com/archives/%s/p%s`, s.Event.Channel, strings.ReplaceAll(s.Event.ID, ".", "")),
|
||||||
|
Channel: s.Event.Channel,
|
||||||
|
Thread: s.Event.ID,
|
||||||
|
EventName: strings.Split(s.Event.Attachments[0].Title, ": Firing: ")[1],
|
||||||
|
Event: strings.Split(s.Event.Attachments[0].Title, ":")[0],
|
||||||
|
Plaintext: s.Event.Attachments[0].Text,
|
||||||
|
Asset: "TODO",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return Message{
|
||||||
|
ID: fmt.Sprintf("%s/%v", s.Event.ParentID, s.TS),
|
||||||
|
TS: s.TS,
|
||||||
|
Source: fmt.Sprintf(`https://renderinc.slack.com/archives/%s/p%s`, s.Event.Channel, strings.ReplaceAll(s.Event.ParentID, ".", "")),
|
||||||
|
Channel: s.Event.Channel,
|
||||||
|
Thread: s.Event.ParentID,
|
||||||
|
EventName: "TODO",
|
||||||
|
Event: "TODO",
|
||||||
|
Plaintext: s.Event.Text,
|
||||||
|
Asset: "TODO",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSlack(b []byte) (slackMessage, error) {
|
||||||
|
var result slackMessage
|
||||||
|
err := json.Unmarshal(b, &result)
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|||||||
113
message_test.go
Normal file
113
message_test.go
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseSlackTestdata(t *testing.T) {
|
||||||
|
cases := map[string]struct {
|
||||||
|
slackMessage slackMessage
|
||||||
|
message Message
|
||||||
|
}{
|
||||||
|
"human_thread_message_from_opsgenie_alert.json": {
|
||||||
|
slackMessage: slackMessage{
|
||||||
|
TS: 1712930706,
|
||||||
|
Event: slackEvent{
|
||||||
|
ID: "1712930706.598629",
|
||||||
|
Channel: "C06U1DDBBU4",
|
||||||
|
ParentID: "1712927439.728409",
|
||||||
|
Text: "I gotta do this",
|
||||||
|
Blocks: []slackBlock{{
|
||||||
|
Elements: []slackElement{{
|
||||||
|
Elements: []slackElement{{
|
||||||
|
RichText: "I gotta do this",
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
Bot: slackBot{
|
||||||
|
Name: "",
|
||||||
|
},
|
||||||
|
Attachments: []slackAttachment{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
message: Message{
|
||||||
|
ID: "1712927439.728409/1712930706",
|
||||||
|
TS: 1712930706,
|
||||||
|
Source: "https://renderinc.slack.com/archives/C06U1DDBBU4/p1712927439728409",
|
||||||
|
Channel: "C06U1DDBBU4",
|
||||||
|
Thread: "1712927439.728409",
|
||||||
|
EventName: "TODO",
|
||||||
|
Event: "TODO",
|
||||||
|
Plaintext: "I gotta do this",
|
||||||
|
Asset: "TODO",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"opsgenie_alert.json": {
|
||||||
|
slackMessage: slackMessage{
|
||||||
|
TS: 1712927439,
|
||||||
|
Event: slackEvent{
|
||||||
|
ID: "1712927439.728409",
|
||||||
|
Channel: "C06U1DDBBU4",
|
||||||
|
Bot: slackBot{
|
||||||
|
Name: "Opsgenie for Alert Management",
|
||||||
|
},
|
||||||
|
Attachments: []slackAttachment{{
|
||||||
|
Color: "F4511E",
|
||||||
|
Title: "#11071: [Grafana]: Firing: Alertconfig Workflow Failed",
|
||||||
|
Text: "At least one alertconfig run has failed unexpectedly.\nDashboard: <https://grafana.render.com/d/VLZU83YVk?orgId=1>\nPanel: <https://grafana.render.com/d/VLZU83YVk?orgId=1&viewPanel=17>\nSource: <https://grafana.render.com/alerting/grafana/fa7b06b8-b4d8-4979-bce7-5e1c432edd81/view?orgId=1>",
|
||||||
|
Fields: []slackField{
|
||||||
|
{Value: "P3", Title: "Priority"},
|
||||||
|
{Value: "alertname:Alertconfig Workflow Failed, grafana_folder:Datastores, rule_uid:a7639f7e-6950-41be-850a-b22119f74cbb", Title: "Tags"},
|
||||||
|
{Value: "Datastores Non-Critical", Title: "Routed Teams"},
|
||||||
|
},
|
||||||
|
Actions: []slackAction{{}, {}, {}},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
message: Message{
|
||||||
|
ID: "1712927439.728409/1712927439",
|
||||||
|
TS: 1712927439,
|
||||||
|
Source: "https://renderinc.slack.com/archives/C06U1DDBBU4/p1712927439728409",
|
||||||
|
Channel: "C06U1DDBBU4",
|
||||||
|
Thread: "1712927439.728409",
|
||||||
|
EventName: "Alertconfig Workflow Failed",
|
||||||
|
Event: "#11071",
|
||||||
|
Plaintext: "At least one alertconfig run has failed unexpectedly.\nDashboard: <https://grafana.render.com/d/VLZU83YVk?orgId=1>\nPanel: <https://grafana.render.com/d/VLZU83YVk?orgId=1&viewPanel=17>\nSource: <https://grafana.render.com/alerting/grafana/fa7b06b8-b4d8-4979-bce7-5e1c432edd81/view?orgId=1>",
|
||||||
|
Asset: "TODO",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, d := range cases {
|
||||||
|
want := d
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
b, err := os.ReadFile(path.Join("testdata", "slack_events", name))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("parseSlack", func(t *testing.T) {
|
||||||
|
got, err := parseSlack(b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if fmt.Sprintf("%+v", got) != fmt.Sprintf("%+v", want.slackMessage) {
|
||||||
|
t.Errorf("wanted \n\t%+v, got\n\t%+v", want.slackMessage, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ParseSlack", func(t *testing.T) {
|
||||||
|
got, err := ParseSlack(b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got != want.message {
|
||||||
|
t.Errorf("wanted \n\t%+v, got\n\t%+v", want.message, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
4
queue.go
4
queue.go
@@ -11,10 +11,6 @@ type Queue struct {
|
|||||||
driver Driver
|
driver Driver
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTestQueue() Queue {
|
|
||||||
return Queue{driver: NewTestDB()}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewQueue(driver Driver) Queue {
|
func NewQueue(driver Driver) Queue {
|
||||||
return Queue{driver: driver}
|
return Queue{driver: driver}
|
||||||
}
|
}
|
||||||
|
|||||||
37
queue_test.go
Normal file
37
queue_test.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestQueue(t *testing.T) {
|
||||||
|
ctx, can := context.WithTimeout(context.Background(), time.Second*10)
|
||||||
|
defer can()
|
||||||
|
|
||||||
|
db := NewTestDBIn(t.TempDir())
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
q := NewQueue(db)
|
||||||
|
|
||||||
|
for i := 0; i < 39; i++ {
|
||||||
|
if err := q.Push(ctx, Message{ID: strconv.Itoa(i), TS: uint64(i)}); err != nil {
|
||||||
|
t.Fatal(i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
found := map[uint64]struct{}{}
|
||||||
|
for i := 0; i < 39; i++ {
|
||||||
|
if m, err := q.PeekFirst(ctx); err != nil {
|
||||||
|
t.Fatal(i, err)
|
||||||
|
} else if _, ok := found[m.TS]; ok {
|
||||||
|
t.Error(i, m.TS)
|
||||||
|
} else if err := q.Ack(ctx, m.ID); err != nil {
|
||||||
|
t.Fatal(i, err)
|
||||||
|
} else {
|
||||||
|
found[m.TS] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,10 +13,6 @@ type Storage struct {
|
|||||||
driver Driver
|
driver Driver
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTestStorage() Storage {
|
|
||||||
return Storage{driver: NewTestDB()}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewStorage(driver Driver) Storage {
|
func NewStorage(driver Driver) Storage {
|
||||||
return Storage{driver: driver}
|
return Storage{driver: driver}
|
||||||
}
|
}
|
||||||
|
|||||||
36
storage_test.go
Normal file
36
storage_test.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStorage(t *testing.T) {
|
||||||
|
ctx, can := context.WithTimeout(context.Background(), time.Second)
|
||||||
|
defer can()
|
||||||
|
|
||||||
|
db := NewTestDBIn(t.TempDir())
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
s := NewStorage(db)
|
||||||
|
|
||||||
|
if _, err := s.Get(ctx, "id"); err != ErrNotFound {
|
||||||
|
t.Error("failed to get 404", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m := Message{
|
||||||
|
ID: "id",
|
||||||
|
TS: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Upsert(ctx, m); err != nil {
|
||||||
|
t.Error("failed to upsert", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if m2, err := s.Get(ctx, "id"); err != nil {
|
||||||
|
t.Error("failed to get", err)
|
||||||
|
} else if m != m2 {
|
||||||
|
t.Error(m2)
|
||||||
|
}
|
||||||
|
}
|
||||||
8
testdata/slack_events.json
vendored
8
testdata/slack_events.json
vendored
File diff suppressed because one or more lines are too long
50
testdata/slack_events/human_message.json
vendored
Normal file
50
testdata/slack_events/human_message.json
vendored
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"hint": "a message",
|
||||||
|
"token": "redacted",
|
||||||
|
"team_id": "T9RQLQ0KV",
|
||||||
|
"context_team_id": "T9RQLQ0KV",
|
||||||
|
"context_enterprise_id": null,
|
||||||
|
"api_app_id": "A06TYH7CALB",
|
||||||
|
"event": {
|
||||||
|
"user": "U06868T6ADV",
|
||||||
|
"type": "message",
|
||||||
|
"ts": "1712878479.415559",
|
||||||
|
"client_msg_id": "fce57841-0446-4720-aa1d-557e162c7667",
|
||||||
|
"text": "BOTH OF YOU GET IN HERE HEEEHEEEHEEE",
|
||||||
|
"team": "T9RQLQ0KV",
|
||||||
|
"blocks": [
|
||||||
|
{
|
||||||
|
"type": "rich_text",
|
||||||
|
"block_id": "6G5BZ",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"type": "rich_text_section",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "BOTH OF YOU GET IN HERE HEEEHEEEHEEE"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"channel": "C06U1DDBBU4",
|
||||||
|
"event_ts": "1712878479.415559",
|
||||||
|
"channel_type": "channel"
|
||||||
|
},
|
||||||
|
"type": "event_callback",
|
||||||
|
"event_id": "Ev06U1F01XMJ",
|
||||||
|
"event_time": 1712878479,
|
||||||
|
"authorizations": [
|
||||||
|
{
|
||||||
|
"enterprise_id": null,
|
||||||
|
"team_id": "T9RQLQ0KV",
|
||||||
|
"user_id": "U06TS9M7ABG",
|
||||||
|
"is_bot": true,
|
||||||
|
"is_enterprise_install": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_ext_shared_channel": false,
|
||||||
|
"event_context": "4-eyJldCI6Im1lc3NhZ2UiLCJ0aWQiOiJUOVJRTFEwS1YiLCJhaWQiOiJBMDZUWUg3Q0FMQiIsImNpZCI6IkMwNlUxRERCQlU0In0"
|
||||||
|
}
|
||||||
52
testdata/slack_events/human_thread_message.json
vendored
Normal file
52
testdata/slack_events/human_thread_message.json
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"hint": "a thread message",
|
||||||
|
"token": "redacted",
|
||||||
|
"team_id": "T9RQLQ0KV",
|
||||||
|
"context_team_id": "T9RQLQ0KV",
|
||||||
|
"context_enterprise_id": null,
|
||||||
|
"api_app_id": "A06TYH7CALB",
|
||||||
|
"event": {
|
||||||
|
"user": "U06868T6ADV",
|
||||||
|
"type": "message",
|
||||||
|
"ts": "1712878566.650149",
|
||||||
|
"client_msg_id": "5d9d586b-eee0-40f8-b15e-66245c073a4c",
|
||||||
|
"text": "in a thread",
|
||||||
|
"team": "T9RQLQ0KV",
|
||||||
|
"thread_ts": "1712877772.926539",
|
||||||
|
"parent_user_id": "U06868T6ADV",
|
||||||
|
"blocks": [
|
||||||
|
{
|
||||||
|
"type": "rich_text",
|
||||||
|
"block_id": "KHSCu",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"type": "rich_text_section",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "in a thread"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"channel": "C06U1DDBBU4",
|
||||||
|
"event_ts": "1712878566.650149",
|
||||||
|
"channel_type": "channel"
|
||||||
|
},
|
||||||
|
"type": "event_callback",
|
||||||
|
"event_id": "Ev06TW3WE58V",
|
||||||
|
"event_time": 1712878566,
|
||||||
|
"authorizations": [
|
||||||
|
{
|
||||||
|
"enterprise_id": null,
|
||||||
|
"team_id": "T9RQLQ0KV",
|
||||||
|
"user_id": "U06TS9M7ABG",
|
||||||
|
"is_bot": true,
|
||||||
|
"is_enterprise_install": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_ext_shared_channel": false,
|
||||||
|
"event_context": "4-eyJldCI6Im1lc3NhZ2UiLCJ0aWQiOiJUOVJRTFEwS1YiLCJhaWQiOiJBMDZUWUg3Q0FMQiIsImNpZCI6IkMwNlUxRERCQlU0In0"
|
||||||
|
}
|
||||||
52
testdata/slack_events/human_thread_message_from_opsgenie_alert.json
vendored
Normal file
52
testdata/slack_events/human_thread_message_from_opsgenie_alert.json
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"hint": "a thread reply to an alert",
|
||||||
|
"token": "redacted",
|
||||||
|
"team_id": "T9RQLQ0KV",
|
||||||
|
"context_team_id": "T9RQLQ0KV",
|
||||||
|
"context_enterprise_id": null,
|
||||||
|
"api_app_id": "A06TYH7CALB",
|
||||||
|
"event": {
|
||||||
|
"user": "U06868T6ADV",
|
||||||
|
"type": "message",
|
||||||
|
"ts": "1712930706.598629",
|
||||||
|
"client_msg_id": "880fc69f-de95-4c1e-90a7-aae701a40c21",
|
||||||
|
"text": "I gotta do this",
|
||||||
|
"team": "T9RQLQ0KV",
|
||||||
|
"thread_ts": "1712927439.728409",
|
||||||
|
"parent_user_id": "U03RUK7FBUY",
|
||||||
|
"blocks": [
|
||||||
|
{
|
||||||
|
"type": "rich_text",
|
||||||
|
"block_id": "Cp0IU",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"type": "rich_text_section",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "I gotta do this"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"channel": "C06U1DDBBU4",
|
||||||
|
"event_ts": "1712930706.598629",
|
||||||
|
"channel_type": "channel"
|
||||||
|
},
|
||||||
|
"type": "event_callback",
|
||||||
|
"event_id": "Ev06UF3U1LM7",
|
||||||
|
"event_time": 1712930706,
|
||||||
|
"authorizations": [
|
||||||
|
{
|
||||||
|
"enterprise_id": null,
|
||||||
|
"team_id": "T9RQLQ0KV",
|
||||||
|
"user_id": "U06TS9M7ABG",
|
||||||
|
"is_bot": true,
|
||||||
|
"is_enterprise_install": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_ext_shared_channel": false,
|
||||||
|
"event_context": "4-eyJldCI6Im1lc3NhZ2UiLCJ0aWQiOiJUOVJRTFEwS1YiLCJhaWQiOiJBMDZUWUg3Q0FMQiIsImNpZCI6IkMwNlUxRERCQlU0In0"
|
||||||
|
}
|
||||||
127
testdata/slack_events/opsgenie_alert.json
vendored
Normal file
127
testdata/slack_events/opsgenie_alert.json
vendored
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
{
|
||||||
|
"hint": "a new alert from opsgenie",
|
||||||
|
"token": "redacted",
|
||||||
|
"team_id": "T9RQLQ0KV",
|
||||||
|
"context_team_id": "T9RQLQ0KV",
|
||||||
|
"context_enterprise_id": null,
|
||||||
|
"api_app_id": "A06TYH7CALB",
|
||||||
|
"event": {
|
||||||
|
"user": "U03RUK7FBUY",
|
||||||
|
"type": "message",
|
||||||
|
"ts": "1712927439.728409",
|
||||||
|
"bot_id": "B03RHGBPH2M",
|
||||||
|
"app_id": "A286WATV2",
|
||||||
|
"text": "",
|
||||||
|
"team": "T9RQLQ0KV",
|
||||||
|
"bot_profile": {
|
||||||
|
"id": "B03RHGBPH2M",
|
||||||
|
"deleted": false,
|
||||||
|
"name": "Opsgenie for Alert Management",
|
||||||
|
"updated": 1658887059,
|
||||||
|
"app_id": "A286WATV2",
|
||||||
|
"icons": {
|
||||||
|
"image_36": "https://avatars.slack-edge.com/2019-05-30/652285939191_7831939cc30ef7159561_36.png",
|
||||||
|
"image_48": "https://avatars.slack-edge.com/2019-05-30/652285939191_7831939cc30ef7159561_48.png",
|
||||||
|
"image_72": "https://avatars.slack-edge.com/2019-05-30/652285939191_7831939cc30ef7159561_72.png"
|
||||||
|
},
|
||||||
|
"team_id": "T9RQLQ0KV"
|
||||||
|
},
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"color": "F4511E",
|
||||||
|
"fallback": "New alert: \"[Grafana]: Firing: Alertconfig Workflow Failed\" <https://opsg.in/a/i/render/38152bc5-bc5d-411d-9feb-d285af5b6481-1712927439305|11071>\nTags: alertname:Alertconfig Workflow Failed, grafana_folder:Datastores, rule_uid:a7639f7e-6950-41be-850a-b22119f74cbb",
|
||||||
|
"text": "At least one alertconfig run has failed unexpectedly.\nDashboard: <https://grafana.render.com/d/VLZU83YVk?orgId=1>\nPanel: <https://grafana.render.com/d/VLZU83YVk?orgId=1&viewPanel=17>\nSource: <https://grafana.render.com/alerting/grafana/fa7b06b8-b4d8-4979-bce7-5e1c432edd81/view?orgId=1>",
|
||||||
|
"title": "#11071: [Grafana]: Firing: Alertconfig Workflow Failed",
|
||||||
|
"title_link": "https://opsg.in/a/i/render/38152bc5-bc5d-411d-9feb-d285af5b6481-1712927439305",
|
||||||
|
"author_name": "New Alert created via Grafana - Datastore Slack",
|
||||||
|
"callback_id": "bbd4a269-08a9-470e-ba79-ce238ac03dc7_05fa2e9b-bec4-4a7e-842d-36043d267a13_11071",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"value": "P3",
|
||||||
|
"title": "Priority",
|
||||||
|
"short": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "alertname:Alertconfig Workflow Failed, grafana_folder:Datastores, rule_uid:a7639f7e-6950-41be-850a-b22119f74cbb",
|
||||||
|
"title": "Tags",
|
||||||
|
"short": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "Datastores Non-Critical",
|
||||||
|
"title": "Routed Teams",
|
||||||
|
"short": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"mrkdwn_in": [
|
||||||
|
"pretext",
|
||||||
|
"text"
|
||||||
|
],
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"id": "1",
|
||||||
|
"name": "acknowledge",
|
||||||
|
"text": "Acknowledge",
|
||||||
|
"type": "button",
|
||||||
|
"value": "ack",
|
||||||
|
"style": "default"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2",
|
||||||
|
"name": "close",
|
||||||
|
"text": "Close",
|
||||||
|
"type": "button",
|
||||||
|
"value": "close",
|
||||||
|
"style": "primary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3",
|
||||||
|
"name": "action",
|
||||||
|
"text": "Other actions...",
|
||||||
|
"type": "select",
|
||||||
|
"data_source": "static",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"text": "Assign",
|
||||||
|
"value": "assign"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Take Ownership",
|
||||||
|
"value": "own"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Snooze",
|
||||||
|
"value": "snooze"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Add Note",
|
||||||
|
"value": "addNote"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Update Priority",
|
||||||
|
"value": "updatePriority"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"channel": "C06U1DDBBU4",
|
||||||
|
"event_ts": "1712927439.728409",
|
||||||
|
"channel_type": "channel"
|
||||||
|
},
|
||||||
|
"type": "event_callback",
|
||||||
|
"event_id": "Ev06UEPF5BSM",
|
||||||
|
"event_time": 1712927439,
|
||||||
|
"authorizations": [
|
||||||
|
{
|
||||||
|
"enterprise_id": null,
|
||||||
|
"team_id": "T9RQLQ0KV",
|
||||||
|
"user_id": "U06TS9M7ABG",
|
||||||
|
"is_bot": true,
|
||||||
|
"is_enterprise_install": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_ext_shared_channel": false,
|
||||||
|
"event_context": "4-eyJldCI6Im1lc3NhZ2UiLCJ0aWQiOiJUOVJRTFEwS1YiLCJhaWQiOiJBMDZUWUg3Q0FMQiIsImNpZCI6IkMwNlUxRERCQlU0In0"
|
||||||
|
}
|
||||||
192
testdata/slack_events/opsgenie_alert_2.json
vendored
Normal file
192
testdata/slack_events/opsgenie_alert_2.json
vendored
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
{
|
||||||
|
"hint": "a new alert from opsgenie",
|
||||||
|
"token": "redacted",
|
||||||
|
"team_id": "T9RQLQ0KV",
|
||||||
|
"context_team_id": "T9RQLQ0KV",
|
||||||
|
"context_enterprise_id": null,
|
||||||
|
"api_app_id": "A06TYH7CALB",
|
||||||
|
"event": {
|
||||||
|
"type": "message",
|
||||||
|
"subtype": "message_changed",
|
||||||
|
"message": {
|
||||||
|
"user": "U03RUK7FBUY",
|
||||||
|
"type": "message",
|
||||||
|
"edited": {
|
||||||
|
"user": "B03RHGBPH2M",
|
||||||
|
"ts": "1712925631.000000"
|
||||||
|
},
|
||||||
|
"bot_id": "B03RHGBPH2M",
|
||||||
|
"app_id": "A286WATV2",
|
||||||
|
"text": "",
|
||||||
|
"team": "T9RQLQ0KV",
|
||||||
|
"bot_profile": {
|
||||||
|
"id": "B03RHGBPH2M",
|
||||||
|
"app_id": "A286WATV2",
|
||||||
|
"name": "Opsgenie for Alert Management",
|
||||||
|
"icons": {
|
||||||
|
"image_36": "https://avatars.slack-edge.com/2019-05-30/652285939191_7831939cc30ef7159561_36.png",
|
||||||
|
"image_48": "https://avatars.slack-edge.com/2019-05-30/652285939191_7831939cc30ef7159561_48.png",
|
||||||
|
"image_72": "https://avatars.slack-edge.com/2019-05-30/652285939191_7831939cc30ef7159561_72.png"
|
||||||
|
},
|
||||||
|
"deleted": false,
|
||||||
|
"updated": 1658887059,
|
||||||
|
"team_id": "T9RQLQ0KV"
|
||||||
|
},
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"color": "2ecc71",
|
||||||
|
"fallback": "\"[Grafana]: Firing: Alertconfig Workflow Failed\" <https://opsg.in/a/i/render/ec6e7c4b-90a6-4c3c-94e9-901f6b7ada47-1712922031403|11070>\nTags: alertname:Alertconfig Workflow Failed, grafana_folder:Datastores, rule_uid:a7639f7e-6950-41be-850a-b22119f74cbb",
|
||||||
|
"text": "At least one alertconfig run has failed unexpectedly.\nDashboard: <https://grafana.render.com/d/VLZU83YVk?orgId=1>\nPanel: <https://grafana.render.com/d/VLZU83YVk?orgId=1&viewPanel=17>\nSource: <https://grafana.render.com/alerting/grafana/fa7b06b8-b4d8-4979-bce7-5e1c432edd81/view?orgId=1>",
|
||||||
|
"title": "#11070: [Grafana]: Firing: Alertconfig Workflow Failed",
|
||||||
|
"title_link": "https://opsg.in/a/i/render/ec6e7c4b-90a6-4c3c-94e9-901f6b7ada47-1712922031403",
|
||||||
|
"callback_id": "bbd4a269-08a9-470e-ba79-ce238ac03dc7_05fa2e9b-bec4-4a7e-842d-36043d267a13_11070",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"value": "P3",
|
||||||
|
"title": "Priority",
|
||||||
|
"short": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "alertname:Alertconfig Workflow Failed, grafana_folder:Datastores, rule_uid:a7639f7e-6950-41be-850a-b22119f74cbb",
|
||||||
|
"title": "Tags",
|
||||||
|
"short": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "Datastores Non-Critical",
|
||||||
|
"title": "Routed Teams",
|
||||||
|
"short": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"mrkdwn_in": [
|
||||||
|
"text"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ts": "1712922031.821339",
|
||||||
|
"source_team": "T9RQLQ0KV",
|
||||||
|
"user_team": "T9RQLQ0KV"
|
||||||
|
},
|
||||||
|
"previous_message": {
|
||||||
|
"user": "U03RUK7FBUY",
|
||||||
|
"type": "message",
|
||||||
|
"ts": "1712922031.821339",
|
||||||
|
"bot_id": "B03RHGBPH2M",
|
||||||
|
"app_id": "A286WATV2",
|
||||||
|
"text": "",
|
||||||
|
"team": "T9RQLQ0KV",
|
||||||
|
"bot_profile": {
|
||||||
|
"id": "B03RHGBPH2M",
|
||||||
|
"app_id": "A286WATV2",
|
||||||
|
"name": "Opsgenie for Alert Management",
|
||||||
|
"icons": {
|
||||||
|
"image_36": "https://avatars.slack-edge.com/2019-05-30/652285939191_7831939cc30ef7159561_36.png",
|
||||||
|
"image_48": "https://avatars.slack-edge.com/2019-05-30/652285939191_7831939cc30ef7159561_48.png",
|
||||||
|
"image_72": "https://avatars.slack-edge.com/2019-05-30/652285939191_7831939cc30ef7159561_72.png"
|
||||||
|
},
|
||||||
|
"deleted": false,
|
||||||
|
"updated": 1658887059,
|
||||||
|
"team_id": "T9RQLQ0KV"
|
||||||
|
},
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"color": "F4511E",
|
||||||
|
"fallback": "New alert: \"[Grafana]: Firing: Alertconfig Workflow Failed\" <https://opsg.in/a/i/render/ec6e7c4b-90a6-4c3c-94e9-901f6b7ada47-1712922031403|11070>\nTags: alertname:Alertconfig Workflow Failed, grafana_folder:Datastores, rule_uid:a7639f7e-6950-41be-850a-b22119f74cbb",
|
||||||
|
"text": "At least one alertconfig run has failed unexpectedly.\nDashboard: <https://grafana.render.com/d/VLZU83YVk?orgId=1>\nPanel: <https://grafana.render.com/d/VLZU83YVk?orgId=1&viewPanel=17>\nSource: <https://grafana.render.com/alerting/grafana/fa7b06b8-b4d8-4979-bce7-5e1c432edd81/view?orgId=1>",
|
||||||
|
"title": "#11070: [Grafana]: Firing: Alertconfig Workflow Failed",
|
||||||
|
"title_link": "https://opsg.in/a/i/render/ec6e7c4b-90a6-4c3c-94e9-901f6b7ada47-1712922031403",
|
||||||
|
"author_name": "New Alert created via Grafana - Datastore Slack",
|
||||||
|
"callback_id": "bbd4a269-08a9-470e-ba79-ce238ac03dc7_05fa2e9b-bec4-4a7e-842d-36043d267a13_11070",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"value": "P3",
|
||||||
|
"title": "Priority",
|
||||||
|
"short": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "alertname:Alertconfig Workflow Failed, grafana_folder:Datastores, rule_uid:a7639f7e-6950-41be-850a-b22119f74cbb",
|
||||||
|
"title": "Tags",
|
||||||
|
"short": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "Datastores Non-Critical",
|
||||||
|
"title": "Routed Teams",
|
||||||
|
"short": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"mrkdwn_in": [
|
||||||
|
"pretext",
|
||||||
|
"text"
|
||||||
|
],
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"id": "1",
|
||||||
|
"name": "acknowledge",
|
||||||
|
"text": "Acknowledge",
|
||||||
|
"type": "button",
|
||||||
|
"value": "ack",
|
||||||
|
"style": "default"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2",
|
||||||
|
"name": "close",
|
||||||
|
"text": "Close",
|
||||||
|
"type": "button",
|
||||||
|
"value": "close",
|
||||||
|
"style": "primary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3",
|
||||||
|
"name": "action",
|
||||||
|
"text": "Other actions...",
|
||||||
|
"type": "select",
|
||||||
|
"data_source": "static",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"text": "Assign",
|
||||||
|
"value": "assign"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Take Ownership",
|
||||||
|
"value": "own"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Snooze",
|
||||||
|
"value": "snooze"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Add Note",
|
||||||
|
"value": "addNote"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Update Priority",
|
||||||
|
"value": "updatePriority"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"channel": "C06U1DDBBU4",
|
||||||
|
"hidden": true,
|
||||||
|
"ts": "1712925631.000400",
|
||||||
|
"event_ts": "1712925631.000400",
|
||||||
|
"channel_type": "channel"
|
||||||
|
},
|
||||||
|
"type": "event_callback",
|
||||||
|
"event_id": "Ev06UQNCJKNC",
|
||||||
|
"event_time": 1712925631,
|
||||||
|
"authorizations": [
|
||||||
|
{
|
||||||
|
"enterprise_id": null,
|
||||||
|
"team_id": "T9RQLQ0KV",
|
||||||
|
"user_id": "U06TS9M7ABG",
|
||||||
|
"is_bot": true,
|
||||||
|
"is_enterprise_install": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_ext_shared_channel": false,
|
||||||
|
"event_context": "4-eyJldCI6Im1lc3NhZ2UiLCJ0aWQiOiJUOVJRTFEwS1YiLCJhaWQiOiJBMDZUWUg3Q0FMQiIsImNpZCI6IkMwNlUxRERCQlU0In0"
|
||||||
|
}
|
||||||
127
testdata/slack_events/opsgenie_alert_3.json
vendored
Normal file
127
testdata/slack_events/opsgenie_alert_3.json
vendored
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
{
|
||||||
|
"hint": "an alert firing that has a render_id",
|
||||||
|
"token": "redacted",
|
||||||
|
"team_id": "T9RQLQ0KV",
|
||||||
|
"context_team_id": "T9RQLQ0KV",
|
||||||
|
"context_enterprise_id": null,
|
||||||
|
"api_app_id": "A06TYH7CALB",
|
||||||
|
"event": {
|
||||||
|
"user": "U03RUK7FBUY",
|
||||||
|
"type": "message",
|
||||||
|
"ts": "1712911957.023359",
|
||||||
|
"bot_id": "B03RHGBPH2M",
|
||||||
|
"app_id": "A286WATV2",
|
||||||
|
"text": "",
|
||||||
|
"team": "T9RQLQ0KV",
|
||||||
|
"bot_profile": {
|
||||||
|
"id": "B03RHGBPH2M",
|
||||||
|
"deleted": false,
|
||||||
|
"name": "Opsgenie for Alert Management",
|
||||||
|
"updated": 1658887059,
|
||||||
|
"app_id": "A286WATV2",
|
||||||
|
"icons": {
|
||||||
|
"image_36": "https://avatars.slack-edge.com/2019-05-30/652285939191_7831939cc30ef7159561_36.png",
|
||||||
|
"image_48": "https://avatars.slack-edge.com/2019-05-30/652285939191_7831939cc30ef7159561_48.png",
|
||||||
|
"image_72": "https://avatars.slack-edge.com/2019-05-30/652285939191_7831939cc30ef7159561_72.png"
|
||||||
|
},
|
||||||
|
"team_id": "T9RQLQ0KV"
|
||||||
|
},
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"color": "F4511E",
|
||||||
|
"fallback": "New alert: \"[Grafana]: Firing: [Oregon-1] Wal Receive Count Alert\" <https://opsg.in/a/i/render/c1565736-aac4-4a78-b7db-65e4d90a59f2-1712911956739|11067>\nTags: alertname:[oregon-1] WAL Receive Count alert, business_hours:true, cluster:oregon-1, contacts:\"OpsGenie - Datastore\",\"Slack - Datastore, grafana_folder:[Generated] oregon-1, template-source:ab1da08c-129f-4f08-b505-db44929eb8",
|
||||||
|
"text": "Replica for dpg-cn7te6nsc6pc73ak5pig-b cannot start streaming replication, because the primary has already deleted the needed WAL segment.\n\nSync the replica from scratch using the runbook.\nRunbook: <https://slab.render.com/posts/runbook-postgres-replica-too-far-behind-pr084dj4>\nSource: <https://grafana.render.com/alerting/grafana/aecdfbcf-5298-4679-bd20-d9dec32ee2a0/view?orgId=1>",
|
||||||
|
"title": "#11067: [Grafana]: Firing: [Oregon-1] Wal Receive Count Alert",
|
||||||
|
"title_link": "https://opsg.in/a/i/render/c1565736-aac4-4a78-b7db-65e4d90a59f2-1712911956739",
|
||||||
|
"author_name": "New Alert created via Grafana - Datastore Slack",
|
||||||
|
"callback_id": "bbd4a269-08a9-470e-ba79-ce238ac03dc7_05fa2e9b-bec4-4a7e-842d-36043d267a13_11067",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"value": "P3",
|
||||||
|
"title": "Priority",
|
||||||
|
"short": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "alertname:[oregon-1] WAL Receive Count alert, business_hours:true, cluster:oregon-1, contacts:\"OpsGenie - Datastore\",\"Slack - Datastore, grafana_folder:[Generated] oregon-1, template-source:ab1da08c-129f-4f08-b505-db44929eb8",
|
||||||
|
"title": "Tags",
|
||||||
|
"short": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "Datastores Non-Critical",
|
||||||
|
"title": "Routed Teams",
|
||||||
|
"short": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"mrkdwn_in": [
|
||||||
|
"pretext",
|
||||||
|
"text"
|
||||||
|
],
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"id": "1",
|
||||||
|
"name": "acknowledge",
|
||||||
|
"text": "Acknowledge",
|
||||||
|
"type": "button",
|
||||||
|
"value": "ack",
|
||||||
|
"style": "default"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2",
|
||||||
|
"name": "close",
|
||||||
|
"text": "Close",
|
||||||
|
"type": "button",
|
||||||
|
"value": "close",
|
||||||
|
"style": "primary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3",
|
||||||
|
"name": "action",
|
||||||
|
"text": "Other actions...",
|
||||||
|
"type": "select",
|
||||||
|
"data_source": "static",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"text": "Assign",
|
||||||
|
"value": "assign"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Take Ownership",
|
||||||
|
"value": "own"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Snooze",
|
||||||
|
"value": "snooze"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Add Note",
|
||||||
|
"value": "addNote"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Update Priority",
|
||||||
|
"value": "updatePriority"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"channel": "C06U1DDBBU4",
|
||||||
|
"event_ts": "1712911957.023359",
|
||||||
|
"channel_type": "channel"
|
||||||
|
},
|
||||||
|
"type": "event_callback",
|
||||||
|
"event_id": "Ev06U0TNBXV0",
|
||||||
|
"event_time": 1712911957,
|
||||||
|
"authorizations": [
|
||||||
|
{
|
||||||
|
"enterprise_id": null,
|
||||||
|
"team_id": "T9RQLQ0KV",
|
||||||
|
"user_id": "U06TS9M7ABG",
|
||||||
|
"is_bot": true,
|
||||||
|
"is_enterprise_install": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_ext_shared_channel": false,
|
||||||
|
"event_context": "4-eyJldCI6Im1lc3NhZ2UiLCJ0aWQiOiJUOVJRTFEwS1YiLCJhaWQiOiJBMDZUWUg3Q0FMQiIsImNpZCI6IkMwNlUxRERCQlU0In0"
|
||||||
|
}
|
||||||
192
testdata/slack_events/opsgenie_alert_resolved.json
vendored
Normal file
192
testdata/slack_events/opsgenie_alert_resolved.json
vendored
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
{
|
||||||
|
"hint": "an edit to resolve an alert from opsgenie",
|
||||||
|
"token": "redacted",
|
||||||
|
"team_id": "T9RQLQ0KV",
|
||||||
|
"context_team_id": "T9RQLQ0KV",
|
||||||
|
"context_enterprise_id": null,
|
||||||
|
"api_app_id": "A06TYH7CALB",
|
||||||
|
"event": {
|
||||||
|
"type": "message",
|
||||||
|
"subtype": "message_changed",
|
||||||
|
"message": {
|
||||||
|
"user": "U03RUK7FBUY",
|
||||||
|
"type": "message",
|
||||||
|
"edited": {
|
||||||
|
"user": "B03RHGBPH2M",
|
||||||
|
"ts": "1712916339.000000"
|
||||||
|
},
|
||||||
|
"bot_id": "B03RHGBPH2M",
|
||||||
|
"app_id": "A286WATV2",
|
||||||
|
"text": "",
|
||||||
|
"team": "T9RQLQ0KV",
|
||||||
|
"bot_profile": {
|
||||||
|
"id": "B03RHGBPH2M",
|
||||||
|
"app_id": "A286WATV2",
|
||||||
|
"name": "Opsgenie for Alert Management",
|
||||||
|
"icons": {
|
||||||
|
"image_36": "https://avatars.slack-edge.com/2019-05-30/652285939191_7831939cc30ef7159561_36.png",
|
||||||
|
"image_48": "https://avatars.slack-edge.com/2019-05-30/652285939191_7831939cc30ef7159561_48.png",
|
||||||
|
"image_72": "https://avatars.slack-edge.com/2019-05-30/652285939191_7831939cc30ef7159561_72.png"
|
||||||
|
},
|
||||||
|
"deleted": false,
|
||||||
|
"updated": 1658887059,
|
||||||
|
"team_id": "T9RQLQ0KV"
|
||||||
|
},
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"color": "2ecc71",
|
||||||
|
"fallback": "\"[Grafana]: Firing: Alertconfig Workflow Failed\" <https://opsg.in/a/i/render/ba6f8300-b869-4aba-9d02-c142237ae59e-1712912739431|11069>\nTags: alertname:Alertconfig Workflow Failed, grafana_folder:Datastores, rule_uid:a7639f7e-6950-41be-850a-b22119f74cbb",
|
||||||
|
"text": "At least one alertconfig run has failed unexpectedly.\nDashboard: <https://grafana.render.com/d/VLZU83YVk?orgId=1>\nPanel: <https://grafana.render.com/d/VLZU83YVk?orgId=1&viewPanel=17>\nSource: <https://grafana.render.com/alerting/grafana/fa7b06b8-b4d8-4979-bce7-5e1c432edd81/view?orgId=1>",
|
||||||
|
"title": "#11069: [Grafana]: Firing: Alertconfig Workflow Failed",
|
||||||
|
"title_link": "https://opsg.in/a/i/render/ba6f8300-b869-4aba-9d02-c142237ae59e-1712912739431",
|
||||||
|
"callback_id": "bbd4a269-08a9-470e-ba79-ce238ac03dc7_05fa2e9b-bec4-4a7e-842d-36043d267a13_11069",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"value": "P3",
|
||||||
|
"title": "Priority",
|
||||||
|
"short": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "alertname:Alertconfig Workflow Failed, grafana_folder:Datastores, rule_uid:a7639f7e-6950-41be-850a-b22119f74cbb",
|
||||||
|
"title": "Tags",
|
||||||
|
"short": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "Datastores Non-Critical",
|
||||||
|
"title": "Routed Teams",
|
||||||
|
"short": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"mrkdwn_in": [
|
||||||
|
"text"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ts": "1712912739.723049",
|
||||||
|
"source_team": "T9RQLQ0KV",
|
||||||
|
"user_team": "T9RQLQ0KV"
|
||||||
|
},
|
||||||
|
"previous_message": {
|
||||||
|
"user": "U03RUK7FBUY",
|
||||||
|
"type": "message",
|
||||||
|
"ts": "1712912739.723049",
|
||||||
|
"bot_id": "B03RHGBPH2M",
|
||||||
|
"app_id": "A286WATV2",
|
||||||
|
"text": "",
|
||||||
|
"team": "T9RQLQ0KV",
|
||||||
|
"bot_profile": {
|
||||||
|
"id": "B03RHGBPH2M",
|
||||||
|
"app_id": "A286WATV2",
|
||||||
|
"name": "Opsgenie for Alert Management",
|
||||||
|
"icons": {
|
||||||
|
"image_36": "https://avatars.slack-edge.com/2019-05-30/652285939191_7831939cc30ef7159561_36.png",
|
||||||
|
"image_48": "https://avatars.slack-edge.com/2019-05-30/652285939191_7831939cc30ef7159561_48.png",
|
||||||
|
"image_72": "https://avatars.slack-edge.com/2019-05-30/652285939191_7831939cc30ef7159561_72.png"
|
||||||
|
},
|
||||||
|
"deleted": false,
|
||||||
|
"updated": 1658887059,
|
||||||
|
"team_id": "T9RQLQ0KV"
|
||||||
|
},
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"color": "F4511E",
|
||||||
|
"fallback": "New alert: \"[Grafana]: Firing: Alertconfig Workflow Failed\" <https://opsg.in/a/i/render/ba6f8300-b869-4aba-9d02-c142237ae59e-1712912739431|11069>\nTags: alertname:Alertconfig Workflow Failed, grafana_folder:Datastores, rule_uid:a7639f7e-6950-41be-850a-b22119f74cbb",
|
||||||
|
"text": "At least one alertconfig run has failed unexpectedly.\nDashboard: <https://grafana.render.com/d/VLZU83YVk?orgId=1>\nPanel: <https://grafana.render.com/d/VLZU83YVk?orgId=1&viewPanel=17>\nSource: <https://grafana.render.com/alerting/grafana/fa7b06b8-b4d8-4979-bce7-5e1c432edd81/view?orgId=1>",
|
||||||
|
"title": "#11069: [Grafana]: Firing: Alertconfig Workflow Failed",
|
||||||
|
"title_link": "https://opsg.in/a/i/render/ba6f8300-b869-4aba-9d02-c142237ae59e-1712912739431",
|
||||||
|
"author_name": "New Alert created via Grafana - Datastore Slack",
|
||||||
|
"callback_id": "bbd4a269-08a9-470e-ba79-ce238ac03dc7_05fa2e9b-bec4-4a7e-842d-36043d267a13_11069",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"value": "P3",
|
||||||
|
"title": "Priority",
|
||||||
|
"short": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "alertname:Alertconfig Workflow Failed, grafana_folder:Datastores, rule_uid:a7639f7e-6950-41be-850a-b22119f74cbb",
|
||||||
|
"title": "Tags",
|
||||||
|
"short": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "Datastores Non-Critical",
|
||||||
|
"title": "Routed Teams",
|
||||||
|
"short": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"mrkdwn_in": [
|
||||||
|
"pretext",
|
||||||
|
"text"
|
||||||
|
],
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"id": "1",
|
||||||
|
"name": "acknowledge",
|
||||||
|
"text": "Acknowledge",
|
||||||
|
"type": "button",
|
||||||
|
"value": "ack",
|
||||||
|
"style": "default"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2",
|
||||||
|
"name": "close",
|
||||||
|
"text": "Close",
|
||||||
|
"type": "button",
|
||||||
|
"value": "close",
|
||||||
|
"style": "primary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3",
|
||||||
|
"name": "action",
|
||||||
|
"text": "Other actions...",
|
||||||
|
"type": "select",
|
||||||
|
"data_source": "static",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"text": "Assign",
|
||||||
|
"value": "assign"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Take Ownership",
|
||||||
|
"value": "own"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Snooze",
|
||||||
|
"value": "snooze"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Add Note",
|
||||||
|
"value": "addNote"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Update Priority",
|
||||||
|
"value": "updatePriority"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"channel": "C06U1DDBBU4",
|
||||||
|
"hidden": true,
|
||||||
|
"ts": "1712916339.000300",
|
||||||
|
"event_ts": "1712916339.000300",
|
||||||
|
"channel_type": "channel"
|
||||||
|
},
|
||||||
|
"type": "event_callback",
|
||||||
|
"event_id": "Ev06TLMY8FJB",
|
||||||
|
"event_time": 1712916339,
|
||||||
|
"authorizations": [
|
||||||
|
{
|
||||||
|
"enterprise_id": null,
|
||||||
|
"team_id": "T9RQLQ0KV",
|
||||||
|
"user_id": "U06TS9M7ABG",
|
||||||
|
"is_bot": true,
|
||||||
|
"is_enterprise_install": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_ext_shared_channel": false,
|
||||||
|
"event_context": "4-eyJldCI6Im1lc3NhZ2UiLCJ0aWQiOiJUOVJRTFEwS1YiLCJhaWQiOiJBMDZUWUg3Q0FMQiIsImNpZCI6IkMwNlUxRERCQlU0In0"
|
||||||
|
}
|
||||||
55
testdata/slack_events/opsgenie_alert_resolved_fyi.json
vendored
Normal file
55
testdata/slack_events/opsgenie_alert_resolved_fyi.json
vendored
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"hint": "a teeny closed alert message",
|
||||||
|
"token": "redacted",
|
||||||
|
"team_id": "T9RQLQ0KV",
|
||||||
|
"context_team_id": "T9RQLQ0KV",
|
||||||
|
"context_enterprise_id": null,
|
||||||
|
"api_app_id": "A06TYH7CALB",
|
||||||
|
"event": {
|
||||||
|
"user": "U03RUK7FBUY",
|
||||||
|
"type": "message",
|
||||||
|
"ts": "1712925631.682559",
|
||||||
|
"bot_id": "B03RHGBPH2M",
|
||||||
|
"app_id": "A286WATV2",
|
||||||
|
"text": "",
|
||||||
|
"team": "T9RQLQ0KV",
|
||||||
|
"bot_profile": {
|
||||||
|
"id": "B03RHGBPH2M",
|
||||||
|
"deleted": false,
|
||||||
|
"name": "Opsgenie for Alert Management",
|
||||||
|
"updated": 1658887059,
|
||||||
|
"app_id": "A286WATV2",
|
||||||
|
"icons": {
|
||||||
|
"image_36": "https://avatars.slack-edge.com/2019-05-30/652285939191_7831939cc30ef7159561_36.png",
|
||||||
|
"image_48": "https://avatars.slack-edge.com/2019-05-30/652285939191_7831939cc30ef7159561_48.png",
|
||||||
|
"image_72": "https://avatars.slack-edge.com/2019-05-30/652285939191_7831939cc30ef7159561_72.png"
|
||||||
|
},
|
||||||
|
"team_id": "T9RQLQ0KV"
|
||||||
|
},
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"color": "2ecc71",
|
||||||
|
"fallback": "Alert API closed alert <https://opsg.in/a/i/render/ec6e7c4b-90a6-4c3c-94e9-901f6b7ada47-1712922031403|#11070> \"[Grafana]: Firing: Alertconfig Workflow Failed\"",
|
||||||
|
"text": "Alert API closed alert <https://opsg.in/a/i/render/ec6e7c4b-90a6-4c3c-94e9-901f6b7ada47-1712922031403|#11070> \"[Grafana]: Firing: Alertconfig Workflow Failed\""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"channel": "C06U1DDBBU4",
|
||||||
|
"event_ts": "1712925631.682559",
|
||||||
|
"channel_type": "channel"
|
||||||
|
},
|
||||||
|
"type": "event_callback",
|
||||||
|
"event_id": "Ev06U1R10TPV",
|
||||||
|
"event_time": 1712925631,
|
||||||
|
"authorizations": [
|
||||||
|
{
|
||||||
|
"enterprise_id": null,
|
||||||
|
"team_id": "T9RQLQ0KV",
|
||||||
|
"user_id": "U06TS9M7ABG",
|
||||||
|
"is_bot": true,
|
||||||
|
"is_enterprise_install": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_ext_shared_channel": false,
|
||||||
|
"event_context": "4-eyJldCI6Im1lc3NhZ2UiLCJ0aWQiOiJUOVJRTFEwS1YiLCJhaWQiOiJBMDZUWUg3Q0FMQiIsImNpZCI6IkMwNlUxRERCQlU0In0"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user