mirror of
https://github.com/SigNoz/signoz.git
synced 2026-09-18 09:20:41 +01:00
Compare commits
2 Commits
feat/heatm
...
nv/slack-m
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e78ce2cc9a | ||
|
|
06d97b7e8d |
@@ -384,13 +384,49 @@ components:
|
||||
required:
|
||||
- routingKey
|
||||
type: object
|
||||
AlertmanagertypesChannelSlackAction:
|
||||
properties:
|
||||
confirm:
|
||||
$ref: '#/components/schemas/AlertmanagertypesChannelSlackConfirmation'
|
||||
name:
|
||||
type: string
|
||||
style:
|
||||
type: string
|
||||
text:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
url:
|
||||
type: string
|
||||
value:
|
||||
type: string
|
||||
required:
|
||||
- type
|
||||
- text
|
||||
type: object
|
||||
AlertmanagertypesChannelSlackConfig:
|
||||
properties:
|
||||
actions:
|
||||
items:
|
||||
$ref: '#/components/schemas/AlertmanagertypesChannelSlackAction'
|
||||
type: array
|
||||
apiUrl:
|
||||
format: password
|
||||
type: string
|
||||
channel:
|
||||
type: string
|
||||
color:
|
||||
type: string
|
||||
fallback:
|
||||
type: string
|
||||
fields:
|
||||
items:
|
||||
$ref: '#/components/schemas/AlertmanagertypesChannelSlackField'
|
||||
type: array
|
||||
footer:
|
||||
type: string
|
||||
pretext:
|
||||
type: string
|
||||
sendResolved:
|
||||
nullable: true
|
||||
type: boolean
|
||||
@@ -398,9 +434,37 @@ components:
|
||||
type: string
|
||||
title:
|
||||
type: string
|
||||
titleLink:
|
||||
type: string
|
||||
required:
|
||||
- apiUrl
|
||||
type: object
|
||||
AlertmanagertypesChannelSlackConfirmation:
|
||||
properties:
|
||||
dismissText:
|
||||
type: string
|
||||
okText:
|
||||
type: string
|
||||
text:
|
||||
type: string
|
||||
title:
|
||||
type: string
|
||||
required:
|
||||
- text
|
||||
type: object
|
||||
AlertmanagertypesChannelSlackField:
|
||||
properties:
|
||||
short:
|
||||
nullable: true
|
||||
type: boolean
|
||||
title:
|
||||
type: string
|
||||
value:
|
||||
type: string
|
||||
required:
|
||||
- title
|
||||
- value
|
||||
type: object
|
||||
AlertmanagertypesChannelWebhookConfig:
|
||||
properties:
|
||||
bearerToken:
|
||||
|
||||
@@ -40,7 +40,73 @@ export interface AlertmanagertypesChannelDTO {
|
||||
export enum AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelSlackConfigDTOKind {
|
||||
slack = 'slack',
|
||||
}
|
||||
export interface AlertmanagertypesChannelSlackConfirmationDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
dismissText?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
okText?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
text: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface AlertmanagertypesChannelSlackActionDTO {
|
||||
confirm?: AlertmanagertypesChannelSlackConfirmationDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
style?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
text: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
type: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
url?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
value?: string;
|
||||
}
|
||||
|
||||
export interface AlertmanagertypesChannelSlackFieldDTO {
|
||||
/**
|
||||
* @type boolean,null
|
||||
*/
|
||||
short?: boolean | null;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
title: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface AlertmanagertypesChannelSlackConfigDTO {
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
actions?: AlertmanagertypesChannelSlackActionDTO[];
|
||||
/**
|
||||
* @type string
|
||||
* @format password
|
||||
@@ -50,6 +116,26 @@ export interface AlertmanagertypesChannelSlackConfigDTO {
|
||||
* @type string
|
||||
*/
|
||||
channel?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
color?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
fallback?: string;
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
fields?: AlertmanagertypesChannelSlackFieldDTO[];
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
footer?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
pretext?: string;
|
||||
/**
|
||||
* @type boolean,null
|
||||
*/
|
||||
@@ -62,6 +148,10 @@ export interface AlertmanagertypesChannelSlackConfigDTO {
|
||||
* @type string
|
||||
*/
|
||||
title?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
titleLink?: string;
|
||||
}
|
||||
|
||||
export interface AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelSlackConfigDTO {
|
||||
|
||||
@@ -45,11 +45,27 @@ func TestPostableChannelValidate(t *testing.T) {
|
||||
postable PostableNotificationChannel
|
||||
}{
|
||||
{
|
||||
description: "webhook password without username",
|
||||
description: "webhook basic auth combined with bearer token",
|
||||
postable: PostableNotificationChannel{
|
||||
Name: "hook",
|
||||
DisplayName: "hook",
|
||||
Config: ChannelConfig{Kind: ChannelKindWebhook, Spec: &ChannelWebhookConfig{URL: "https://a", Password: "p"}},
|
||||
Config: ChannelConfig{Kind: ChannelKindWebhook, Spec: &ChannelWebhookConfig{URL: "https://a", Password: "p", BearerToken: "t"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "slack field without a value",
|
||||
postable: PostableNotificationChannel{
|
||||
Name: "slack",
|
||||
DisplayName: "slack",
|
||||
Config: ChannelConfig{Kind: ChannelKindSlack, Spec: &ChannelSlackConfig{APIURL: "https://a", Fields: []ChannelSlackField{{Title: "Severity"}}}},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "slack action with neither url nor name",
|
||||
postable: PostableNotificationChannel{
|
||||
Name: "slack",
|
||||
DisplayName: "slack",
|
||||
Config: ChannelConfig{Kind: ChannelKindSlack, Spec: &ChannelSlackConfig{APIURL: "https://a", Actions: []ChannelSlackAction{{Type: "button", Text: "Open"}}}},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -211,6 +211,38 @@ type ChannelSlackConfig struct {
|
||||
Channel string `json:"channel"`
|
||||
Title valuer.UnsetOrNonEmptyString `json:"title"`
|
||||
Text valuer.UnsetOrNonEmptyString `json:"text"`
|
||||
Color valuer.UnsetOrNonEmptyString `json:"color"`
|
||||
TitleLink valuer.UnsetOrNonEmptyString `json:"titleLink"`
|
||||
Pretext valuer.UnsetOrNonEmptyString `json:"pretext"`
|
||||
Fallback valuer.UnsetOrNonEmptyString `json:"fallback"`
|
||||
Footer valuer.UnsetOrNonEmptyString `json:"footer"`
|
||||
Fields []ChannelSlackField `json:"fields,omitempty"`
|
||||
Actions []ChannelSlackAction `json:"actions,omitempty"`
|
||||
}
|
||||
|
||||
type ChannelSlackField struct {
|
||||
Title string `json:"title" required:"true"`
|
||||
Value string `json:"value" required:"true"`
|
||||
Short *bool `json:"short,omitempty"`
|
||||
}
|
||||
|
||||
// ChannelSlackAction is a link button when URL is set, otherwise a message
|
||||
// button that needs Name. Upstream clears whichever side is not in use.
|
||||
type ChannelSlackAction struct {
|
||||
Type string `json:"type" required:"true"`
|
||||
Text string `json:"text" required:"true"`
|
||||
URL string `json:"url"`
|
||||
Style string `json:"style"`
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
Confirm *ChannelSlackConfirmation `json:"confirm,omitempty"`
|
||||
}
|
||||
|
||||
type ChannelSlackConfirmation struct {
|
||||
Text string `json:"text" required:"true"`
|
||||
Title string `json:"title"`
|
||||
OkText string `json:"okText"`
|
||||
DismissText string `json:"dismissText"`
|
||||
}
|
||||
|
||||
func (c ChannelSlackConfig) Validate() error {
|
||||
@@ -218,6 +250,24 @@ func (c ChannelSlackConfig) Validate() error {
|
||||
return errors.NewInvalidInputf(ErrCodeAlertmanagerChannelInvalid, "config.spec.apiUrl is required for a slack channel")
|
||||
}
|
||||
|
||||
for i, field := range c.Fields {
|
||||
if field.Title == "" || field.Value == "" {
|
||||
return errors.NewInvalidInputf(ErrCodeAlertmanagerChannelInvalid, "config.spec.fields[%d] requires title and value", i)
|
||||
}
|
||||
}
|
||||
|
||||
for i, action := range c.Actions {
|
||||
if action.Type == "" || action.Text == "" {
|
||||
return errors.NewInvalidInputf(ErrCodeAlertmanagerChannelInvalid, "config.spec.actions[%d] requires type and text", i)
|
||||
}
|
||||
if action.URL == "" && action.Name == "" {
|
||||
return errors.NewInvalidInputf(ErrCodeAlertmanagerChannelInvalid, "config.spec.actions[%d] requires url or name", i)
|
||||
}
|
||||
if action.Confirm != nil && action.Confirm.Text == "" {
|
||||
return errors.NewInvalidInputf(ErrCodeAlertmanagerChannelInvalid, "config.spec.actions[%d].confirm requires text", i)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -235,6 +285,13 @@ func (c ChannelSlackConfig) toUndefaultedReceiver(displayName string) (*Receiver
|
||||
Channel: c.Channel,
|
||||
Title: c.Title.StringValue(),
|
||||
Text: c.Text.StringValue(),
|
||||
Color: c.Color.StringValue(),
|
||||
TitleLink: c.TitleLink.StringValue(),
|
||||
Pretext: c.Pretext.StringValue(),
|
||||
Fallback: c.Fallback.StringValue(),
|
||||
Footer: c.Footer.StringValue(),
|
||||
Fields: newUpstreamSlackFields(c.Fields),
|
||||
Actions: newUpstreamSlackActions(c.Actions),
|
||||
}},
|
||||
}}, nil
|
||||
}
|
||||
@@ -253,9 +310,76 @@ func newChannelSlackConfigFromReceiver(name string, receiver *Receiver) (Channel
|
||||
Channel: slack.Channel,
|
||||
Title: valuer.UnsetIfEmpty(slack.Title),
|
||||
Text: valuer.UnsetIfEmpty(slack.Text),
|
||||
Color: valuer.UnsetIfEmpty(slack.Color),
|
||||
TitleLink: valuer.UnsetIfEmpty(slack.TitleLink),
|
||||
Pretext: valuer.UnsetIfEmpty(slack.Pretext),
|
||||
Fallback: valuer.UnsetIfEmpty(slack.Fallback),
|
||||
Footer: valuer.UnsetIfEmpty(slack.Footer),
|
||||
Fields: newChannelSlackFields(slack.Fields),
|
||||
Actions: newChannelSlackActions(slack.Actions),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func newUpstreamSlackFields(fields []ChannelSlackField) []*config.SlackField {
|
||||
if len(fields) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
upstream := make([]*config.SlackField, 0, len(fields))
|
||||
for _, field := range fields {
|
||||
upstream = append(upstream, &config.SlackField{Title: field.Title, Value: field.Value, Short: field.Short})
|
||||
}
|
||||
|
||||
return upstream
|
||||
}
|
||||
|
||||
func newChannelSlackFields(upstream []*config.SlackField) []ChannelSlackField {
|
||||
if len(upstream) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
fields := make([]ChannelSlackField, 0, len(upstream))
|
||||
for _, field := range upstream {
|
||||
fields = append(fields, ChannelSlackField{Title: field.Title, Value: field.Value, Short: field.Short})
|
||||
}
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
func newUpstreamSlackActions(actions []ChannelSlackAction) []*config.SlackAction {
|
||||
if len(actions) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
upstream := make([]*config.SlackAction, 0, len(actions))
|
||||
for _, action := range actions {
|
||||
upstreamAction := &config.SlackAction{Type: action.Type, Text: action.Text, URL: action.URL, Style: action.Style, Name: action.Name, Value: action.Value}
|
||||
if action.Confirm != nil {
|
||||
upstreamAction.ConfirmField = &config.SlackConfirmationField{Text: action.Confirm.Text, Title: action.Confirm.Title, OkText: action.Confirm.OkText, DismissText: action.Confirm.DismissText}
|
||||
}
|
||||
upstream = append(upstream, upstreamAction)
|
||||
}
|
||||
|
||||
return upstream
|
||||
}
|
||||
|
||||
func newChannelSlackActions(upstream []*config.SlackAction) []ChannelSlackAction {
|
||||
if len(upstream) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
actions := make([]ChannelSlackAction, 0, len(upstream))
|
||||
for _, upstreamAction := range upstream {
|
||||
action := ChannelSlackAction{Type: upstreamAction.Type, Text: upstreamAction.Text, URL: upstreamAction.URL, Style: upstreamAction.Style, Name: upstreamAction.Name, Value: upstreamAction.Value}
|
||||
if upstreamAction.ConfirmField != nil {
|
||||
action.Confirm = &ChannelSlackConfirmation{Text: upstreamAction.ConfirmField.Text, Title: upstreamAction.ConfirmField.Title, OkText: upstreamAction.ConfirmField.OkText, DismissText: upstreamAction.ConfirmField.DismissText}
|
||||
}
|
||||
actions = append(actions, action)
|
||||
}
|
||||
|
||||
return actions
|
||||
}
|
||||
|
||||
// ChannelEmailConfig carries no SMTP transport fields: the smarthost,
|
||||
// credentials and TLS settings come from the deployment's global config, so a
|
||||
// channel can only choose recipients and body.
|
||||
@@ -309,7 +433,8 @@ func newChannelEmailConfigFromReceiver(_ string, receiver *Receiver) (ChannelSpe
|
||||
|
||||
// ChannelWebhookConfig splits apart the two authentication modes the legacy API
|
||||
// overloaded onto one password field, where an empty username meant the password
|
||||
// was really a bearer token.
|
||||
// was really a bearer token. Username or Password may be set without the other,
|
||||
// as upstream allows, but not together with BearerToken.
|
||||
type ChannelWebhookConfig struct {
|
||||
SendResolved *bool `json:"sendResolved,omitempty"`
|
||||
URL string `json:"url" required:"true" format:"password"`
|
||||
@@ -329,10 +454,6 @@ func (c ChannelWebhookConfig) Validate() error {
|
||||
return errors.NewInvalidInputf(ErrCodeAlertmanagerChannelInvalid, "config.spec.bearerToken cannot be combined with config.spec.username or config.spec.password")
|
||||
}
|
||||
|
||||
if usesBasicAuth && (c.Username == "" || c.Password == "") {
|
||||
return errors.NewInvalidInputf(ErrCodeAlertmanagerChannelInvalid, "config.spec.username and config.spec.password must both be set for basic auth")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -346,7 +467,7 @@ func (c ChannelWebhookConfig) toUndefaultedReceiver(displayName string) (*Receiv
|
||||
// and EnableHTTP2 marshal unconditionally, so a zero value would persist
|
||||
// them as false and read back as a config ChannelWebhookConfig cannot represent.
|
||||
switch {
|
||||
case c.Username != "":
|
||||
case c.Username != "" || c.Password != "":
|
||||
httpConfig := commoncfg.DefaultHTTPClientConfig
|
||||
httpConfig.BasicAuth = &commoncfg.BasicAuth{
|
||||
Username: c.Username,
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
// mutually exclusive and so cannot all be set at once.
|
||||
func TestChannelToPostableChannelRoundTripsEveryFieldOfEveryKind(t *testing.T) {
|
||||
sendResolved := true
|
||||
short := true
|
||||
|
||||
testCases := []struct {
|
||||
description string
|
||||
@@ -37,6 +38,16 @@ func TestChannelToPostableChannelRoundTripsEveryFieldOfEveryKind(t *testing.T) {
|
||||
Channel: "#alerts",
|
||||
Title: valuer.MustNewUnsetOrNonEmptyString("slack title"),
|
||||
Text: valuer.MustNewUnsetOrNonEmptyString("slack text"),
|
||||
Color: valuer.MustNewUnsetOrNonEmptyString("#439FE0"),
|
||||
TitleLink: valuer.MustNewUnsetOrNonEmptyString("{{ .CommonLabels.ruleSource }}"),
|
||||
Pretext: valuer.MustNewUnsetOrNonEmptyString("slack pretext"),
|
||||
Fallback: valuer.MustNewUnsetOrNonEmptyString("slack fallback"),
|
||||
Footer: valuer.MustNewUnsetOrNonEmptyString("slack footer"),
|
||||
Fields: []ChannelSlackField{{Title: "Severity", Value: "{{ .CommonLabels.severity }}", Short: &short}},
|
||||
Actions: []ChannelSlackAction{
|
||||
{Type: "button", Text: "Open in SigNoz", URL: "{{ .CommonLabels.ruleSource }}", Style: "primary"},
|
||||
{Type: "button", Text: "Acknowledge", Name: "ack", Value: "ack", Confirm: &ChannelSlackConfirmation{Text: "Acknowledge this alert?", Title: "Confirm", OkText: "Yes", DismissText: "No"}},
|
||||
},
|
||||
},
|
||||
expectedRoundTrip: &ChannelSlackConfig{
|
||||
SendResolved: &sendResolved,
|
||||
@@ -44,6 +55,16 @@ func TestChannelToPostableChannelRoundTripsEveryFieldOfEveryKind(t *testing.T) {
|
||||
Channel: "#alerts",
|
||||
Title: valuer.MustNewUnsetOrNonEmptyString("slack title"),
|
||||
Text: valuer.MustNewUnsetOrNonEmptyString("slack text"),
|
||||
Color: valuer.MustNewUnsetOrNonEmptyString("#439FE0"),
|
||||
TitleLink: valuer.MustNewUnsetOrNonEmptyString("{{ .CommonLabels.ruleSource }}"),
|
||||
Pretext: valuer.MustNewUnsetOrNonEmptyString("slack pretext"),
|
||||
Fallback: valuer.MustNewUnsetOrNonEmptyString("slack fallback"),
|
||||
Footer: valuer.MustNewUnsetOrNonEmptyString("slack footer"),
|
||||
Fields: []ChannelSlackField{{Title: "Severity", Value: "{{ .CommonLabels.severity }}", Short: &short}},
|
||||
Actions: []ChannelSlackAction{
|
||||
{Type: "button", Text: "Open in SigNoz", URL: "{{ .CommonLabels.ruleSource }}", Style: "primary"},
|
||||
{Type: "button", Text: "Acknowledge", Name: "ack", Value: "ack", Confirm: &ChannelSlackConfirmation{Text: "Acknowledge this alert?", Title: "Confirm", OkText: "Yes", DismissText: "No"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -27,6 +27,21 @@ _PASSWORD = "password123Z$"
|
||||
"kind,spec,assert_field,assert_value",
|
||||
[
|
||||
pytest.param("slack", {"apiUrl": "https://hooks.slack.test/services/T/B/X", "channel": "#alerts", "title": "Alert", "text": "{{ .CommonLabels.alertname }}"}, "channel", "#alerts", id="slack"),
|
||||
pytest.param(
|
||||
"slack",
|
||||
{
|
||||
"apiUrl": "https://hooks.slack.test/services/T/B/X",
|
||||
"channel": "#alerts",
|
||||
"color": "#439FE0",
|
||||
"titleLink": "{{ .CommonLabels.ruleSource }}",
|
||||
"footer": "platform · terraform",
|
||||
"fields": [{"title": "Severity", "value": "{{ .CommonLabels.severity }}", "short": True}],
|
||||
"actions": [{"type": "button", "text": "Open in SigNoz", "url": "{{ .CommonLabels.ruleSource }}"}],
|
||||
},
|
||||
"fields",
|
||||
[{"title": "Severity", "value": "{{ .CommonLabels.severity }}", "short": True}],
|
||||
id="slack-attachment",
|
||||
),
|
||||
pytest.param("email", {"to": "oncall@integration.test", "html": "<p>{{ .CommonLabels.alertname }}</p>"}, "to", "oncall@integration.test", id="email"),
|
||||
pytest.param("webhook", {"url": "https://webhook.test/hook", "username": "bob", "password": "s3cret"}, "username", "bob", id="webhook"),
|
||||
pytest.param("pagerduty", {"routingKey": "pd-routing-key", "severity": "critical", "class": "db", "description": "{{ .CommonLabels.alertname }}"}, "severity", "critical", id="pagerduty"),
|
||||
@@ -313,9 +328,12 @@ def test_create_rejects_a_duplicate_display_name(
|
||||
pytest.param({"name": "telegram-kind", "config": {"kind": "telegram", "spec": {"chatId": 1}}}, id="unmodelled_kind"),
|
||||
pytest.param({"name": "slack-unknown-field", "config": {"kind": "slack", "spec": {"apiUrl": "https://hooks.slack.test/services/T/B/X", "channel": "#a", "text": "body", "iconEmoji": ":tada:"}}}, id="unknown_spec_field"),
|
||||
pytest.param({"name": "slack-with-email-spec", "config": {"kind": "slack", "spec": {"to": "a@integration.test", "html": "<p>body</p>"}}}, id="spec_of_another_kind"),
|
||||
pytest.param({"name": "slack-field-without-value", "config": {"kind": "slack", "spec": {"apiUrl": "https://hooks.slack.test/services/T/B/X", "fields": [{"title": "Severity"}]}}}, id="slack_field_without_value"),
|
||||
pytest.param({"name": "slack-action-without-text", "config": {"kind": "slack", "spec": {"apiUrl": "https://hooks.slack.test/services/T/B/X", "actions": [{"type": "button", "url": "https://signoz.test"}]}}}, id="slack_action_without_text"),
|
||||
pytest.param({"name": "slack-action-without-target", "config": {"kind": "slack", "spec": {"apiUrl": "https://hooks.slack.test/services/T/B/X", "actions": [{"type": "button", "text": "Open"}]}}}, id="slack_action_without_url_or_name"),
|
||||
pytest.param({"name": "slack-confirm-without-text", "config": {"kind": "slack", "spec": {"apiUrl": "https://hooks.slack.test/services/T/B/X", "actions": [{"type": "button", "text": "Ack", "name": "ack", "confirm": {"title": "Sure?"}}]}}}, id="slack_action_confirm_without_text"),
|
||||
pytest.param({"name": "extra-field", "config": {"kind": "email", "spec": {"to": "a@integration.test", "html": "<p>body</p>"}}, "type": "email"}, id="unknown_envelope_field"),
|
||||
pytest.param({"name": "webhook-both-auth", "config": {"kind": "webhook", "spec": {"url": "https://webhook.test/hook", "username": "u", "password": "p", "bearerToken": "t"}}}, id="webhook_basic_auth_with_bearer_token"),
|
||||
pytest.param({"name": "webhook-half-auth", "config": {"kind": "webhook", "spec": {"url": "https://webhook.test/hook", "username": "u"}}}, id="webhook_basic_auth_without_password"),
|
||||
# The last three reach the notifier's own validation rather than the
|
||||
# spec's, so they assert it still surfaces as a 400 through v2.
|
||||
pytest.param({"name": "jira-server-site", "config": {"kind": "jira", "spec": {"site": "https://jira.acme.com", "project": "OPS", "issueType": "Bug", "email": "a@integration.test", "apiToken": "t", "summary": "Alert", "description": "body"}}}, id="jira_site_not_jira_cloud"),
|
||||
|
||||
Reference in New Issue
Block a user