removed notify stuff (#74)

This commit is contained in:
Neil O'Toole 2020-12-30 12:18:22 -07:00 committed by GitHub
parent 5aebc04356
commit 8345e6743b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 10 additions and 860 deletions

View File

@ -3,9 +3,7 @@
// provides excellent functionality, it has some issues.
// Most prominently, its documentation suggests reliance
// upon package-level constructs for initializing the
// command tree (bad for testing). Also, it doesn't provide support
// for context.Context: see https://github.com/spf13/cobra/pull/893
// which has been lingering for a while at the time of writing.
// command tree (bad for testing).
//
// Thus, this cmd package deviates from cobra's suggested
// usage pattern by eliminating all pkg-level constructs
@ -17,6 +15,11 @@
// an instance of that), but also encapsulates injectable
// resources such as config and logging.
//
// Update (Dec 2020): recent releases of cobra now support
// accessing Context from the cobra.Command. At some point
// it may make sense to revisit the way commands are
// constructed, to use this now-standard cobra mechanism.
//
// The entry point to this pkg is the Execute function.
package cli
@ -95,9 +98,9 @@ func ExecuteWith(rc *RunContext, args []string) error {
rootCmd := newCommandTree(rc)
// The following is a workaround for the fact that cobra doesn't currently
// support executing the root command with arbitrary args. That is to say,
// if you execute:
// The following is a workaround for the fact that cobra doesn't
// currently (as of 2017) support executing the root command with
// arbitrary args. That is to say, if you execute:
//
// sq arg1 arg2
//
@ -114,7 +117,7 @@ func ExecuteWith(rc *RunContext, args []string) error {
panic(fmt.Sprintf("bad cobra cmd state: %v", cmd))
}
// If we have args [sq, arg1, arg2] the we redirect
// If we have args [sq, arg1, arg2] then we redirect
// to the "slq" command by modifying args to
// look like: [query, arg1, arg2] -- noting that SetArgs
// doesn't want the first args element.
@ -179,13 +182,6 @@ func newCommandTree(rc *RunContext) (rootCmd *cobra.Command) {
addCmd(rc, rootCmd, newVersionCmd)
addCmd(rc, rootCmd, newDriversCmd)
notifyCmd := addCmd(rc, rootCmd, newNotifyCmd)
addCmd(rc, notifyCmd, newNotifyListCmd)
addCmd(rc, notifyCmd, newNotifyRemoveCmd)
notifyAddCmd := addCmd(rc, notifyCmd, newNotifyAddCmd)
addCmd(rc, notifyAddCmd, newNotifyAddSlackCmd)
addCmd(rc, notifyAddCmd, newNotifyAddHipChatCmd)
tblCmd := addCmd(rc, rootCmd, newTblCmd)
addCmd(rc, tblCmd, newTblCopyCmd)
addCmd(rc, tblCmd, newTblTruncateCmd)

View File

@ -1,201 +0,0 @@
package cli
import (
"fmt"
"strings"
"github.com/spf13/cobra"
"github.com/neilotoole/sq/libsq/core/errz"
"github.com/neilotoole/sq/libsq/notify"
)
func newNotifyCmd() (*cobra.Command, runFunc) {
cmd := &cobra.Command{
Use: "notify",
Hidden: true, // Not advertising this feature right now
Short: "Manage notification destinations",
Example: `sq notify ls
sq notify add slack devops [...]
sq notify rm devops
sq notify add --help`,
}
return cmd, func(rc *RunContext, cmd *cobra.Command, args []string) error {
return cmd.Help()
}
}
func newNotifyListCmd() (*cobra.Command, runFunc) {
cmd := &cobra.Command{
Use: "ls",
Aliases: []string{"list"},
Short: "List notification destinations",
}
return cmd, execNotifyList
}
func execNotifyList(rc *RunContext, cmd *cobra.Command, args []string) error {
return rc.writers.notifyw.NotifyDestinations(rc.Config.Notification.Destinations)
}
func newNotifyRemoveCmd() (*cobra.Command, runFunc) {
cmd := &cobra.Command{
Use: "rm",
Aliases: []string{"remove"},
Short: "Remove notification destination",
}
return cmd, execNotifyRemove
}
func execNotifyRemove(rc *RunContext, cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errz.Errorf("this command takes exactly one argument")
}
cfg := rc.Config
if len(cfg.Notification.Destinations) == 0 {
return errz.Errorf("the notification destination %q does not exist", args[0])
}
var dests []notify.Destination
for _, dest := range cfg.Notification.Destinations {
if dest.Label == args[0] {
continue
}
dests = append(dests, dest)
}
if len(dests) == len(cfg.Notification.Destinations) {
return errz.Errorf("the notification destination %q does not exist", args[0])
}
cfg.Notification.Destinations = dests
err := rc.ConfigStore.Save(rc.Config)
if err != nil {
return err
}
return nil
}
func newNotifyAddCmd() (*cobra.Command, runFunc) {
cmd := &cobra.Command{
Use: "add",
Short: "Add notification destination",
Example: `sq notify add slack #devops xoxp-892529...911b8a
sq notify add slack --help
sq notify add hipchat myteam ABAD098ASDF...99AB
sq notify add hipchat --help
`,
}
return cmd, nil
}
func newNotifyAddSlackCmd() (*cobra.Command, runFunc) {
cmd := &cobra.Command{
Use: "slack CHANNEL [HANDLE] TOKEN",
Short: "Add Slack channel",
Long: `Add Slack channel. The CHANNEL param should not include the leading '#'.
The HANDLE param is optional; if not provided, a handle
will be generated. To generate the auth token using your browser, login to
https://TEAM.slack.com and then visit https://api.slack.com/custom-integrations/legacy-tokens
and use the "Legacy token generator" to get the token value.`,
Example: `sq notify add slack devops xoxp-892529...911b8a`,
}
return cmd, execNotifyAddSlack
}
func execNotifyAddSlack(rc *RunContext, cmd *cobra.Command, args []string) error {
if len(args) != 2 && len(args) != 3 {
return errz.Errorf(`this command takes either 2 or 3 arguments: see "sq notify add slack --help"`)
}
cfg := rc.Config
labelAvailableFn := func(label string) bool {
for _, dest := range cfg.Notification.Destinations {
if dest.Label == label {
return false
}
}
return true
}
provider, err := notify.ProviderFor("slack")
if err != nil {
return err
}
target := args[0]
var label string
var token string
if len(args) == 2 {
token = args[1]
} else {
label = args[1]
token = args[2]
if !labelAvailableFn(label) {
return errz.Errorf("a notifier with the label %q already exists", label)
}
err = notify.ValidHandle(label)
if err != nil {
return err
}
}
dest, err := provider.Destination(notify.DestType("slack"), target, label, token, labelAvailableFn)
if err != nil {
return err
}
cfg.Notification.Destinations = append(cfg.Notification.Destinations, *dest)
err = rc.ConfigStore.Save(rc.Config)
if err != nil {
return err
}
return rc.writers.notifyw.NotifyDestinations([]notify.Destination{*dest})
}
func newNotifyAddHipChatCmd() (*cobra.Command, runFunc) {
cmd := &cobra.Command{
Use: "hipchat ROOM TOKEN",
Short: "Add HipChat room",
Example: `sq notify add hipchat devops --label="hip_devops" BOuyOe...VRBksq6`,
}
cmd.Flags().String(flagNotifierLabel, "", flagNotifierLabelUsage)
return cmd, execNotifyAddHipChat
}
func execNotifyAddHipChat(rc *RunContext, cmd *cobra.Command, args []string) error {
fmt.Fprintln(rc.Out, "Add HipChat room")
fmt.Fprintln(rc.Out, strings.Join(args, " | "))
var label string
var err error
if cmd.Flags().Changed(flagNotifierLabel) {
label, err = cmd.Flags().GetString(flagNotifierLabel)
if err != nil {
return errz.Err(err)
}
}
if label != "" {
fmt.Fprintf(rc.Out, "Label: %s", label)
}
return nil
}

View File

@ -7,7 +7,6 @@ import (
"github.com/neilotoole/sq/drivers/userdriver"
"github.com/neilotoole/sq/libsq/core/errz"
"github.com/neilotoole/sq/libsq/core/stringz"
"github.com/neilotoole/sq/libsq/notify"
"github.com/neilotoole/sq/libsq/source"
)
@ -23,9 +22,6 @@ type Config struct {
// Sources is the set of data sources.
Sources *source.Set `yaml:"sources" json:"sources"`
// Notifications holds notification config.
Notification *Notification `yaml:"notification" json:"notification"`
// Ext holds sq config extensions, such as user driver config.
Ext Ext `yaml:"-" json:"-"`
}
@ -47,12 +43,6 @@ type Defaults struct {
Header bool `yaml:"output_header" json:"output_header"`
}
// Notification holds notification configuration.
type Notification struct {
Enabled []string `yaml:"enabled" json:"enabled"`
Destinations []notify.Destination `yaml:"destinations" json:"destinations"`
}
// New returns a config instance with default options set.
func New() *Config {
cfg := &Config{}
@ -72,10 +62,6 @@ func initCfg(cfg *Config) {
cfg.Sources = &source.Set{}
}
if cfg.Notification == nil {
cfg.Notification = &Notification{}
}
if cfg.Defaults.Format == "" {
cfg.Defaults.Format = FormatTable
}

View File

@ -1,3 +0,0 @@
// notifiers is the containing package for notifier
// implementations.
package notifiers

View File

@ -1,236 +0,0 @@
// Package hipchat is deprecated.
// Deprecated: don't use me
package hipchat
import (
"strconv"
"net/url"
"fmt"
"strings"
"math"
"bytes"
"time"
"github.com/tbruyelle/hipchat-go/hipchat"
"github.com/neilotoole/sq/libsq/core/errz"
"github.com/neilotoole/sq/libsq/notify"
)
const typ = notify.DestType("hipchat")
func init() {
notify.RegisterProvider(typ, &provider{})
}
type provider struct {
}
func (p *provider) Destination(
typ notify.DestType,
target string,
label string,
credentials string,
labelAvailable func(label string) bool) (*notify.Destination, error) {
if typ != typ {
return nil, errz.Errorf("hipchat: unsupported destination type %q", typ)
}
api := hipchat.NewClient(credentials)
foundRoom, baseURL, err := getRoom(api, target)
if err != nil {
return nil, err
}
canonicalTarget := generateTargetURL(baseURL, strconv.Itoa(foundRoom.ID))
dest := &notify.Destination{Type: typ, Target: canonicalTarget, Credentials: credentials, Label: label}
if dest.Label == "" {
// need to generate the handle
h := foundRoom.Name
// check if we can just grab the room name
if labelAvailable(h) {
dest.Label = h
return dest, nil
}
for i := 1; i < math.MaxInt32; i++ {
h = fmt.Sprintf("%s_%d", foundRoom.Name, i)
if labelAvailable(h) {
dest.Label = h
return dest, nil
}
}
return nil, errz.Errorf("hipchat: unable to suggest label for %q notification destination %q", typ, target)
}
// label was provided by the user, check if it's legal
err = notify.ValidHandle(label)
if err != nil {
return nil, errz.Wrap(err, "hipchat")
}
return dest, nil
}
func getRoom(api *hipchat.Client, target string) (*hipchat.Room, string, error) {
opt := &hipchat.RoomsListOptions{IncludePrivate: true, IncludeArchived: true}
rooms, _, err := api.Room.List(opt)
if err != nil {
return nil, "", errz.Wrapf(err, "hipchat")
}
baseURL, providedRoomID, err := parseTargetURL(target)
if err != nil {
return nil, "", err
}
var foundRoom *hipchat.Room
intRoomID, err := strconv.Atoi(providedRoomID)
if err != nil {
// providedRoomID is not an int, hopefully it's the room name
for _, r := range rooms.Items {
if r.Name == providedRoomID {
foundRoom = &r
break
}
}
} else {
// providedRoomID is an int, it should match room.ID
for _, r := range rooms.Items {
if r.ID == intRoomID {
foundRoom = &r
break
}
}
}
if foundRoom == nil {
return nil, "", errz.Errorf("hipchat: unable to find room matching target %q", target)
}
return foundRoom, baseURL, nil
}
func (p *provider) Notifier(dest notify.Destination) (notify.Notifier, error) {
if dest.Type != typ {
return nil, errz.Errorf("hipchat: unsupported destination type %q", dest.Type)
}
api := hipchat.NewClient(dest.Credentials)
room, _, err := getRoom(api, dest.Target)
if err != nil {
return nil, err
}
return &notifier{dest: dest, api: api, room: room}, nil
}
// parseTargetURL returns the base URL and room ID from a HipChat target URL. For example:
//
// https://api.hipchat.com/v2/room/12345 -> "https://api.hipchat.com/v2/", "12345"
// https://hipchat.acme.com/v2/room/12345 -> "https://hipchat.acme.com/v2/", "12345"
func parseTargetURL(targetURL string) (baseURL, roomID string, err error) {
u, err := url.ParseRequestURI(targetURL)
if err != nil {
return "", "", errz.Wrapf(err, "hipchat: unable to parse target URL %q", targetURL)
}
// e.g. "/v2/room/12345" -> [ "", "v2", "room", "12345"]
pathParts := strings.Split(u.EscapedPath(), "/")
if len(pathParts) < 4 {
return "", "", errz.Errorf("hipchat: the API URL path should have at least 3 parts, but was: %s", targetURL)
}
if pathParts[1] != "v2" {
return "", "", errz.Errorf(`hipchat: only the v2 API is supported, but API URL was: %s`, targetURL)
}
return fmt.Sprintf("https://%s/v2/", u.Host), pathParts[3], nil
}
// generateTargetURL returns a canonical URL identifier for a hipchat room
func generateTargetURL(baseURL string, roomID string) string {
return fmt.Sprintf("%sroom/%s", baseURL, roomID)
}
type notifier struct {
dest notify.Destination
api *hipchat.Client
room *hipchat.Room
}
const tplJobEnded = `<table>
<tr><th>Job ID</th><td><code>%s</code></td></tr>
<tr><th>State</th><td><strong>%s<strong></td></tr>
<tr><th>Started</th><td>%s</td></tr>
<tr><th>Duration</th><td>%s</td></tr>
</table>
<pre>%s</pre>
`
const tplJobNotEnded = `<table>
<tr><th>Job ID</th><td><code>%s</code></td></tr>
<tr><th>State</th><td><strong>%s</strong></td></tr>
<tr><th>Started</th><td>%s</td></tr>
</table>
<pre>%s</pre>
`
func (n *notifier) Send(msg notify.Message) error {
req := &hipchat.NotificationRequest{Message: msg.Text}
req.MessageFormat = "html"
req.From = "sq bot"
buf := &bytes.Buffer{}
buf.WriteString(msg.Text)
if msg.Job != nil {
if buf.Len() > 0 {
buf.WriteString("<br />")
}
switch msg.Job.State {
case notify.Completed:
req.Color = hipchat.ColorGreen
case notify.Failed:
req.Color = hipchat.ColorRed
}
var started string
var duration string
if msg.Job.Started == nil {
started = "-"
} else {
started = msg.Job.Started.Format(time.RFC3339)
}
if msg.Job.Ended == nil {
html := fmt.Sprintf(tplJobNotEnded, msg.Job.ID, msg.Job.State, started, msg.Job.Stmt)
buf.WriteString(html)
} else {
duration = msg.Job.Ended.Sub(*msg.Job.Started).String()
html := fmt.Sprintf(tplJobEnded, msg.Job.ID, msg.Job.State, started, duration, msg.Job.Stmt)
buf.WriteString(html)
}
}
req.Message = buf.String()
_, err := n.api.Room.Notification(strconv.Itoa(n.room.ID), req)
if err != nil {
return errz.Wrap(err, "hipchat")
}
return nil
}

View File

@ -1,110 +0,0 @@
package hipchat
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
"github.com/neilotoole/sq/libsq/notify"
)
const testToken = `Q1dZ4yFDWGMJxt0u9kiBA0yoCNm5Ga0wvs7PO7Uk`
const testRoomName = `notifier-test`
const testRoomID = `3836173`
const baseURL = `https://api.hipchat.com/v2/`
func TestProvider_Destination(t *testing.T) {
t.Skip()
available := func(string) bool {
return true
}
target := fmt.Sprintf("%sroom/%s", baseURL, testRoomName)
p := &provider{}
dest, err := p.Destination(
typ,
target,
"",
testToken,
available)
require.Nil(t, err)
require.NotNil(t, dest)
}
func TestNotifier_Send(t *testing.T) {
t.Skip()
p := &provider{}
dest := newTestDestination(t, p)
n, err := p.Notifier(*dest)
require.Nil(t, err)
require.NotNil(t, n)
err = n.Send(notify.Message{Text: "hello world"})
require.Nil(t, err)
jb := notify.New("@my1.tbluser | .uid, .username, .email")
jb.Start()
err = n.Send(notify.NewJobMessage(*jb))
require.Nil(t, err)
jb.Fail()
err = n.Send(notify.NewJobMessage(*jb))
require.Nil(t, err)
jb = notify.New("@my1.tbluser | .uid, .username, .email")
jb.Start()
err = n.Send(notify.NewJobMessage(*jb))
require.Nil(t, err)
jb.Complete()
err = n.Send(notify.NewJobMessage(*jb))
require.Nil(t, err)
}
func newTestDestination(t *testing.T, p *provider) *notify.Destination {
available := func(string) bool {
return true
}
target := fmt.Sprintf("%sroom/%s", baseURL, testRoomName)
dest, err := p.Destination(
typ,
target,
"",
testToken,
available)
require.Nil(t, err)
return dest
}
// https://api.hipchat.com/v2/room/12345 -> "https://api.hipchat.com/v2/", "12345"
// https://hipchat.acme.com/v2/room/12345 -> "https://hipchat.acme.com/v2/", "12345"
func Test_parseTargetURL(t *testing.T) {
t.Skip()
base, id, err := parseTargetURL("https://api.hipchat.com/v2/room/12345")
require.Nil(t, err)
require.Equal(t, "https://api.hipchat.com/v2/", base)
require.Equal(t, "12345", id)
_, _, err = parseTargetURL("https://api.hipchat.com/v1/room/12345")
require.NotNil(t, err, "only v2 API supported")
_, _, err = parseTargetURL("https://api.hipchat.com/v3/room/12345")
require.NotNil(t, err, "only v3 API supported")
base, id, err = parseTargetURL("https://hipchat.acme.com/v2/room/45678")
require.Nil(t, err)
require.Equal(t, "https://hipchat.acme.com/v2/", base)
require.Equal(t, "45678", id)
}
func TestProvider_generateTargetURL(t *testing.T) {
t.Skip()
val := generateTargetURL("https://api.hipchat.com/v2/", "12345")
require.Equal(t, "https://api.hipchat.com/v2/room/12345", val)
val = generateTargetURL("https://hipchat.acme.com/v2/", "45678")
require.Equal(t, "https://hipchat.acme.com/v2/room/45678", val)
}

View File

@ -1,212 +0,0 @@
// Package slack implements notifications for Slack.
package slack
import (
"fmt"
"math"
"time"
"github.com/nlopes/slack"
"github.com/neilotoole/sq/libsq/core/errz"
"github.com/neilotoole/sq/libsq/notify"
)
const typ = notify.DestType("slack")
func init() {
notify.RegisterProvider(typ, &provider{})
}
type notifier struct {
dest notify.Destination
api *slack.Client
channel *slack.Channel
// threads maps Job IDs to slack timestamps/threads
threads map[string]string
}
func (n *notifier) Send(msg notify.Message) error {
_, err := n.getChannel()
if err != nil {
return err
}
// threadTS holds the slack thread timestamp.
var threadTS string
params := slack.PostMessageParameters{}
params.AsUser = false
params.Username = "sq bot"
if msg.Job != nil {
// let's check if there's already a thread for this Job ID
var ok bool
threadTS, ok = n.threads[msg.Job.ID]
if ok {
// there's already a thread associated with this Job ID; mark the params to indicate this
params.ThreadTimestamp = threadTS
}
att := slack.Attachment{}
if msg.Text == "" {
params.Markdown = true
att.Pretext = "Job " + string(msg.Job.State)
}
att.MarkdownIn = []string{"fields", "pretext"}
switch msg.Job.State {
case notify.Completed:
att.Color = "good"
case notify.Failed:
att.Color = "danger"
}
var started string
var duration string
if msg.Job.Started == nil {
started = "-"
} else {
started = fmt.Sprintf("<!date^%d^{date_num} {time_secs}|%s>", msg.Job.Started.Unix(), msg.Job.Started.Format(time.RFC3339))
}
if msg.Job.Ended == nil {
duration = "-"
} else {
duration = msg.Job.Ended.Sub(*msg.Job.Started).String()
}
att.Fields = []slack.AttachmentField{
{Title: "Job ID", Value: fmt.Sprintf("`%s`", msg.Job.ID), Short: true},
{Title: "State", Value: string(msg.Job.State), Short: true},
{Title: "Query", Value: fmt.Sprintf("```%s```", msg.Job.Stmt)},
{Title: "Started", Value: started, Short: true},
{Title: "Duration", Value: duration, Short: true},
}
params.Attachments = append(params.Attachments, att)
}
_, ts, err := n.api.PostMessage(n.channel.ID, msg.Text, params)
if err != nil {
return errz.Errorf(
"error sending message to Slack channel %q (%q): %v",
n.channel.Name,
n.dest.Label,
err)
}
if msg.Job != nil {
// if threadTS is empty, then this msg is the first in this thread
if threadTS == "" {
// associate the timestamp with the Job ID
n.threads[msg.Job.ID] = ts
}
}
return nil
}
func (n *notifier) getChannel() (*slack.Channel, error) {
if n.channel != nil {
return n.channel, nil
}
channels, err := n.api.GetChannels(true)
if err != nil {
return nil, errz.Err(err)
}
for _, c := range channels {
if c.ID == n.dest.Target {
n.channel = &c
return n.channel, nil
}
}
return nil, errz.Errorf("did not find Slack channel [%s] for notification destination %q", n.dest.Target, n.dest.Label)
}
func getChannel(channelName string, token string) (*slack.Channel, error) {
api := slack.New(token)
channels, err := api.GetChannels(true)
if err != nil {
return nil, errz.Err(err)
}
for _, c := range channels {
if c.Name == channelName {
return &c, nil
}
}
return nil, errz.Errorf("did not find Slack channel %q", channelName)
}
type provider struct {
}
func (p *provider) Destination(
typ notify.DestType,
target string,
label string,
credentials string,
labelAvailable func(label string) bool) (*notify.Destination, error) {
if typ != typ {
return nil, errz.Errorf("unsupported destination type %q", typ)
}
api := slack.New(credentials)
team, err := api.GetTeamInfo()
if err != nil {
return nil, errz.Err(err)
}
channel, err := getChannel(target, credentials)
if err != nil {
return nil, err
}
dest := &notify.Destination{Type: typ, Target: channel.ID, Label: label, Credentials: credentials}
if dest.Label == "" {
// need to generate the handle
h := fmt.Sprintf("%s_%s", team.Name, target)
if labelAvailable(h) {
dest.Label = h
return dest, nil
}
for i := 1; i < math.MaxInt32; i++ {
h = fmt.Sprintf("%s_%s_%d", team.Name, target, i)
if labelAvailable(h) {
dest.Label = h
return dest, nil
}
}
return nil, errz.Errorf("unable to suggest handle for %q notification destination %q", typ, target)
}
err = notify.ValidHandle(label)
if err != nil {
return nil, err
}
return dest, nil
}
func (p *provider) Notifier(dest notify.Destination) (notify.Notifier, error) {
if dest.Type != typ {
return nil, errz.Errorf("unsupported destination type %q", dest.Type)
}
return &notifier{dest: dest, api: slack.New(dest.Credentials), threads: make(map[string]string)}, nil
}

View File

@ -1,70 +0,0 @@
package slack
import (
"testing"
"fmt"
"time"
"github.com/nlopes/slack"
"github.com/stretchr/testify/require"
"github.com/neilotoole/sq/libsq/core/errz"
"github.com/neilotoole/sq/libsq/notify"
)
func TestNotifier_Send(t *testing.T) {
t.Skip()
p := &provider{}
d := newTestDestination(t, p)
n, err := p.Notifier(*d)
require.Nil(t, err)
slackNotifier, ok := n.(*notifier)
require.True(t, ok)
text := fmt.Sprintf("hello world @ %d", time.Now().Unix())
err = slackNotifier.Send(message(text))
require.Nil(t, err)
slackMsg, err := getLatestMessage(slackNotifier)
require.Nil(t, err)
require.Equal(t, text, slackMsg.Text)
}
func message(text string) notify.Message {
return notify.Message{Text: text}
}
func newTestDestination(t *testing.T, p *provider) *notify.Destination {
available := func(handle string) bool {
return true
}
dest, err := p.Destination(
typ,
"notifier-test",
"",
"xoxp-89252929845-89246861280-181096637445-5978e0477c04316bcf348795fc911b8a",
available)
require.Nil(t, err)
return dest
}
func getLatestMessage(n *notifier) (*slack.Message, error) {
channel, err := n.getChannel()
if err != nil {
return nil, err
}
historyParams := slack.HistoryParameters{}
history, err := n.api.GetChannelHistory(channel.ID, historyParams)
if err != nil {
return nil, errz.Err(err)
}
if len(history.Messages) == 0 {
return nil, errz.Errorf("no messages in history for channel %q", n.dest.Label)
}
return &history.Messages[len(history.Messages)-1], nil
}