Merge remote-tracking branch 'origin/master' into sgc/tsm1
# Conflicts: # go.mod # go.sum # query/promql/internal/promqltests/go.modpull/19446/head
commit
56e27b8893
|
@ -150,7 +150,7 @@ jobs:
|
|||
- run: make protoc
|
||||
- run: make build
|
||||
- run:
|
||||
command: ./bin/linux/influxd --store=memory --e2e-testing=true
|
||||
command: ./bin/linux/influxd --store=memory --e2e-testing=true --feature-flags=communityTemplates=true
|
||||
background: true
|
||||
- run: make e2e
|
||||
- store_test_results:
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
1. [19223](https://github.com/influxdata/influxdb/pull/19223): Add dashboards command to influx CLI
|
||||
1. [19225](https://github.com/influxdata/influxdb/pull/19225): Allow user onboarding to optionally set passwords
|
||||
1. [18841](https://github.com/influxdata/influxdb/pull/18841): Limit query response sizes for queries built in QueryBuilder by requiring an aggregate window
|
||||
1. [19135](https://github.com/influxdata/influxdb/pull/19135): Add telegram notification.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
|
|
|
@ -376,22 +376,29 @@ func convertQueries(qs []chronograf.DashboardQuery) []influxdb.DashboardQuery {
|
|||
return ds
|
||||
}
|
||||
|
||||
type dbrpMapper struct {
|
||||
type dbrpMapper struct{}
|
||||
|
||||
// FindBy returns the dbrp mapping for the specified ID.
|
||||
func (d dbrpMapper) FindByID(ctx context.Context, orgID influxdb.ID, id influxdb.ID) (*influxdb.DBRPMappingV2, error) {
|
||||
return nil, errors.New("mapping not found")
|
||||
}
|
||||
|
||||
func (m dbrpMapper) FindBy(ctx context.Context, cluster string, db string, rp string) (*influxdb.DBRPMapping, error) {
|
||||
return nil, errors.New("mapping not found")
|
||||
}
|
||||
func (m dbrpMapper) Find(ctx context.Context, filter influxdb.DBRPMappingFilter) (*influxdb.DBRPMapping, error) {
|
||||
return nil, errors.New("mapping not found")
|
||||
}
|
||||
func (m dbrpMapper) FindMany(ctx context.Context, filter influxdb.DBRPMappingFilter, opt ...influxdb.FindOptions) ([]*influxdb.DBRPMapping, int, error) {
|
||||
// FindMany returns a list of dbrp mappings that match filter and the total count of matching dbrp mappings.
|
||||
func (d dbrpMapper) FindMany(ctx context.Context, dbrp influxdb.DBRPMappingFilterV2, opts ...influxdb.FindOptions) ([]*influxdb.DBRPMappingV2, int, error) {
|
||||
return nil, 0, errors.New("mapping not found")
|
||||
|
||||
}
|
||||
func (m dbrpMapper) Create(ctx context.Context, dbrpMap *influxdb.DBRPMapping) error {
|
||||
|
||||
// Create creates a new dbrp mapping, if a different mapping exists an error is returned.
|
||||
func (d dbrpMapper) Create(ctx context.Context, dbrp *influxdb.DBRPMappingV2) error {
|
||||
return errors.New("dbrpMapper does not support creating new mappings")
|
||||
}
|
||||
func (m dbrpMapper) Delete(ctx context.Context, cluster string, db string, rp string) error {
|
||||
return errors.New("dbrpMapper does not support deleteing mappings")
|
||||
|
||||
// Update a new dbrp mapping
|
||||
func (d dbrpMapper) Update(ctx context.Context, dbrp *influxdb.DBRPMappingV2) error {
|
||||
return errors.New("dbrpMapper does not support updating mappings")
|
||||
}
|
||||
|
||||
// Delete removes a dbrp mapping.
|
||||
func (d dbrpMapper) Delete(ctx context.Context, orgID influxdb.ID, id influxdb.ID) error {
|
||||
return errors.New("dbrpMapper does not support deleting mappings")
|
||||
}
|
||||
|
|
|
@ -62,19 +62,27 @@ func transpileF(cmd *cobra.Command, args []string) error {
|
|||
|
||||
type dbrpMapper struct{}
|
||||
|
||||
func (m dbrpMapper) FindBy(ctx context.Context, cluster string, db string, rp string) (*influxdb.DBRPMapping, error) {
|
||||
// FindBy returns the dbrp mapping for the specified ID.
|
||||
func (d dbrpMapper) FindByID(ctx context.Context, orgID influxdb.ID, id influxdb.ID) (*influxdb.DBRPMappingV2, error) {
|
||||
return nil, errors.New("mapping not found")
|
||||
}
|
||||
func (m dbrpMapper) Find(ctx context.Context, filter influxdb.DBRPMappingFilter) (*influxdb.DBRPMapping, error) {
|
||||
return nil, errors.New("mapping not found")
|
||||
}
|
||||
func (m dbrpMapper) FindMany(ctx context.Context, filter influxdb.DBRPMappingFilter, opt ...influxdb.FindOptions) ([]*influxdb.DBRPMapping, int, error) {
|
||||
return nil, 0, errors.New("mapping not found")
|
||||
|
||||
// FindMany returns a list of dbrp mappings that match filter and the total count of matching dbrp mappings.
|
||||
func (d dbrpMapper) FindMany(ctx context.Context, dbrp influxdb.DBRPMappingFilterV2, opts ...influxdb.FindOptions) ([]*influxdb.DBRPMappingV2, int, error) {
|
||||
return nil, 0, errors.New("mapping not found")
|
||||
}
|
||||
func (m dbrpMapper) Create(ctx context.Context, dbrpMap *influxdb.DBRPMapping) error {
|
||||
|
||||
// Create creates a new dbrp mapping, if a different mapping exists an error is returned.
|
||||
func (d dbrpMapper) Create(ctx context.Context, dbrp *influxdb.DBRPMappingV2) error {
|
||||
return errors.New("dbrpMapper does not support creating new mappings")
|
||||
}
|
||||
func (m dbrpMapper) Delete(ctx context.Context, cluster string, db string, rp string) error {
|
||||
return errors.New("dbrpMapper does not support deleteing mappings")
|
||||
|
||||
// Update a new dbrp mapping
|
||||
func (d dbrpMapper) Update(ctx context.Context, dbrp *influxdb.DBRPMappingV2) error {
|
||||
return errors.New("dbrpMapper does not support updating mappings")
|
||||
}
|
||||
|
||||
// Delete removes a dbrp mapping.
|
||||
func (d dbrpMapper) Delete(ctx context.Context, orgID influxdb.ID, id influxdb.ID) error {
|
||||
return errors.New("dbrpMapper does not support deleting mappings")
|
||||
}
|
||||
|
|
|
@ -11169,6 +11169,7 @@ components:
|
|||
- $ref: "#/components/schemas/SMTPNotificationRule"
|
||||
- $ref: "#/components/schemas/PagerDutyNotificationRule"
|
||||
- $ref: "#/components/schemas/HTTPNotificationRule"
|
||||
- $ref: "#/components/schemas/TelegramNotificationRule"
|
||||
discriminator:
|
||||
propertyName: type
|
||||
mapping:
|
||||
|
@ -11176,6 +11177,7 @@ components:
|
|||
smtp: "#/components/schemas/SMTPNotificationRule"
|
||||
pagerduty: "#/components/schemas/PagerDutyNotificationRule"
|
||||
http: "#/components/schemas/HTTPNotificationRule"
|
||||
telegram: "#/components/schemas/TelegramNotificationRule"
|
||||
NotificationRule:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/NotificationRuleDiscriminator"
|
||||
|
@ -11377,6 +11379,31 @@ components:
|
|||
enum: [pagerduty]
|
||||
messageTemplate:
|
||||
type: string
|
||||
TelegramNotificationRule:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/NotificationRuleBase"
|
||||
- $ref: "#/components/schemas/TelegramNotificationRuleBase"
|
||||
TelegramNotificationRuleBase:
|
||||
type: object
|
||||
required: [type, messageTemplate, channel]
|
||||
properties:
|
||||
type:
|
||||
description: The discriminator between other types of notification rules is "telegram".
|
||||
type: string
|
||||
enum: [telegram]
|
||||
messageTemplate:
|
||||
description: The message template as a flux interpolated string.
|
||||
type: string
|
||||
parseMode:
|
||||
description: Parse mode of the message text per https://core.telegram.org/bots/api#formatting-options . Defaults to "MarkdownV2" .
|
||||
type: string
|
||||
enum:
|
||||
- MarkdownV2
|
||||
- HTML
|
||||
- Markdown
|
||||
disableWebPagePreview:
|
||||
description: Disables preview of web links in the sent messages when "true". Defaults to "false" .
|
||||
type: boolean
|
||||
NotificationEndpointUpdate:
|
||||
type: object
|
||||
|
||||
|
@ -11395,12 +11422,14 @@ components:
|
|||
- $ref: "#/components/schemas/SlackNotificationEndpoint"
|
||||
- $ref: "#/components/schemas/PagerDutyNotificationEndpoint"
|
||||
- $ref: "#/components/schemas/HTTPNotificationEndpoint"
|
||||
- $ref: "#/components/schemas/TelegramNotificationEndpoint"
|
||||
discriminator:
|
||||
propertyName: type
|
||||
mapping:
|
||||
slack: "#/components/schemas/SlackNotificationEndpoint"
|
||||
pagerduty: "#/components/schemas/PagerDutyNotificationEndpoint"
|
||||
http: "#/components/schemas/HTTPNotificationEndpoint"
|
||||
telegram: "#/components/schemas/TelegramNotificationEndpoint"
|
||||
NotificationEndpoint:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/NotificationEndpointDiscrimator"
|
||||
|
@ -11519,9 +11548,22 @@ components:
|
|||
description: Customized headers.
|
||||
additionalProperties:
|
||||
type: string
|
||||
TelegramNotificationEndpoint:
|
||||
type: object
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/NotificationEndpointBase"
|
||||
- type: object
|
||||
required: [token, channel]
|
||||
properties:
|
||||
token:
|
||||
description: Specifies the Telegram bot token. See https://core.telegram.org/bots#creating-a-new-bot .
|
||||
type: string
|
||||
channel:
|
||||
description: ID of the telegram channel, a chat_id in https://core.telegram.org/bots/api#sendmessage .
|
||||
type: string
|
||||
NotificationEndpointType:
|
||||
type: string
|
||||
enum: ["slack", "pagerduty", "http"]
|
||||
enum: ["slack", "pagerduty", "http", "telegram"]
|
||||
DBRP:
|
||||
required:
|
||||
- orgID
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"systemParams": "darwin-x64-83",
|
||||
"modulesFolders": [],
|
||||
"flags": [],
|
||||
"linkedModules": [],
|
||||
"topLevelPatterns": [],
|
||||
"lockfileEntries": {},
|
||||
"files": [],
|
||||
"artifacts": {}
|
||||
}
|
|
@ -12,12 +12,14 @@ const (
|
|||
SlackType = "slack"
|
||||
PagerDutyType = "pagerduty"
|
||||
HTTPType = "http"
|
||||
TelegramType = "telegram"
|
||||
)
|
||||
|
||||
var typeToEndpoint = map[string]func() influxdb.NotificationEndpoint{
|
||||
SlackType: func() influxdb.NotificationEndpoint { return &Slack{} },
|
||||
PagerDutyType: func() influxdb.NotificationEndpoint { return &PagerDuty{} },
|
||||
HTTPType: func() influxdb.NotificationEndpoint { return &HTTP{} },
|
||||
TelegramType: func() influxdb.NotificationEndpoint { return &Telegram{} },
|
||||
}
|
||||
|
||||
// UnmarshalJSON will convert the bytes to notification endpoint.
|
||||
|
|
|
@ -55,7 +55,7 @@ func TestValidEndpoint(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
name: "empty name",
|
||||
name: "empty name PagerDuty",
|
||||
src: &endpoint.PagerDuty{
|
||||
Base: endpoint.Base{
|
||||
ID: influxTesting.MustIDBase16Ptr(id1),
|
||||
|
@ -70,6 +70,22 @@ func TestValidEndpoint(t *testing.T) {
|
|||
Msg: "Notification Endpoint Name can't be empty",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty name Telegram",
|
||||
src: &endpoint.Telegram{
|
||||
Base: endpoint.Base{
|
||||
ID: influxTesting.MustIDBase16Ptr(id1),
|
||||
OrgID: influxTesting.MustIDBase16Ptr(id3),
|
||||
Status: influxdb.Active,
|
||||
},
|
||||
Token: influxdb.SecretField{Key: id1 + "-token"},
|
||||
Channel: "-1001406363649",
|
||||
},
|
||||
err: &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: "Notification Endpoint Name can't be empty",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty slack url",
|
||||
src: &endpoint.Slack{
|
||||
|
@ -136,6 +152,36 @@ func TestValidEndpoint(t *testing.T) {
|
|||
Msg: "invalid http username/password for basic auth",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty telegram token",
|
||||
src: &endpoint.Telegram{
|
||||
Base: goodBase,
|
||||
},
|
||||
err: &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: "empty telegram bot token",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty telegram channel",
|
||||
src: &endpoint.Telegram{
|
||||
Base: goodBase,
|
||||
Token: influxdb.SecretField{Key: id1 + "-token"},
|
||||
},
|
||||
err: &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: "empty telegram channel",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid telegram token",
|
||||
src: &endpoint.Telegram{
|
||||
Base: goodBase,
|
||||
Token: influxdb.SecretField{Key: id1 + "-token"},
|
||||
Channel: "-1001406363649",
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
|
@ -226,6 +272,22 @@ func TestJSON(t *testing.T) {
|
|||
Password: influxdb.SecretField{Key: "password-key"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "simple Telegram",
|
||||
src: &endpoint.Telegram{
|
||||
Base: endpoint.Base{
|
||||
ID: influxTesting.MustIDBase16Ptr(id1),
|
||||
Name: "nameTelegram",
|
||||
OrgID: influxTesting.MustIDBase16Ptr(id3),
|
||||
Status: influxdb.Active,
|
||||
CRUDLog: influxdb.CRUDLog{
|
||||
CreatedAt: timeGen1.Now(),
|
||||
UpdatedAt: timeGen2.Now(),
|
||||
},
|
||||
},
|
||||
Token: influxdb.SecretField{Key: "token-key-1"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
b, err := json.Marshal(c.src)
|
||||
|
@ -365,6 +427,40 @@ func TestBackFill(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "simple Telegram",
|
||||
src: &endpoint.Telegram{
|
||||
Base: endpoint.Base{
|
||||
ID: influxTesting.MustIDBase16Ptr(id1),
|
||||
Name: "name1",
|
||||
OrgID: influxTesting.MustIDBase16Ptr(id3),
|
||||
Status: influxdb.Active,
|
||||
CRUDLog: influxdb.CRUDLog{
|
||||
CreatedAt: timeGen1.Now(),
|
||||
UpdatedAt: timeGen2.Now(),
|
||||
},
|
||||
},
|
||||
Token: influxdb.SecretField{
|
||||
Value: strPtr("token-value"),
|
||||
},
|
||||
},
|
||||
target: &endpoint.Telegram{
|
||||
Base: endpoint.Base{
|
||||
ID: influxTesting.MustIDBase16Ptr(id1),
|
||||
Name: "name1",
|
||||
OrgID: influxTesting.MustIDBase16Ptr(id3),
|
||||
Status: influxdb.Active,
|
||||
CRUDLog: influxdb.CRUDLog{
|
||||
CreatedAt: timeGen1.Now(),
|
||||
UpdatedAt: timeGen2.Now(),
|
||||
},
|
||||
},
|
||||
Token: influxdb.SecretField{
|
||||
Key: id1 + "-token",
|
||||
Value: strPtr("token-value"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
c.src.BackfillSecretKeys()
|
||||
|
@ -374,6 +470,133 @@ func TestBackFill(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSecretFields(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
src influxdb.NotificationEndpoint
|
||||
secrets []influxdb.SecretField
|
||||
}{
|
||||
{
|
||||
name: "simple Slack",
|
||||
src: &endpoint.Slack{
|
||||
Base: endpoint.Base{
|
||||
ID: influxTesting.MustIDBase16Ptr(id1),
|
||||
Name: "name1",
|
||||
OrgID: influxTesting.MustIDBase16Ptr(id3),
|
||||
Status: influxdb.Active,
|
||||
CRUDLog: influxdb.CRUDLog{
|
||||
CreatedAt: timeGen1.Now(),
|
||||
UpdatedAt: timeGen2.Now(),
|
||||
},
|
||||
},
|
||||
URL: "https://slack.com/api/chat.postMessage",
|
||||
Token: influxdb.SecretField{
|
||||
Key: id1 + "-token",
|
||||
Value: strPtr("token-value"),
|
||||
},
|
||||
},
|
||||
secrets: []influxdb.SecretField{
|
||||
{
|
||||
Key: id1 + "-token",
|
||||
Value: strPtr("token-value"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "simple pagerduty",
|
||||
src: &endpoint.PagerDuty{
|
||||
Base: endpoint.Base{
|
||||
ID: influxTesting.MustIDBase16Ptr(id1),
|
||||
Name: "name1",
|
||||
OrgID: influxTesting.MustIDBase16Ptr(id3),
|
||||
Status: influxdb.Active,
|
||||
CRUDLog: influxdb.CRUDLog{
|
||||
CreatedAt: timeGen1.Now(),
|
||||
UpdatedAt: timeGen2.Now(),
|
||||
},
|
||||
},
|
||||
ClientURL: "https://events.pagerduty.com/v2/enqueue",
|
||||
RoutingKey: influxdb.SecretField{
|
||||
Key: id1 + "-routing-key",
|
||||
Value: strPtr("routing-key-value"),
|
||||
},
|
||||
},
|
||||
secrets: []influxdb.SecretField{
|
||||
{
|
||||
Key: id1 + "-routing-key",
|
||||
Value: strPtr("routing-key-value"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "http with user and password",
|
||||
src: &endpoint.HTTP{
|
||||
Base: endpoint.Base{
|
||||
ID: influxTesting.MustIDBase16Ptr(id1),
|
||||
Name: "name1",
|
||||
OrgID: influxTesting.MustIDBase16Ptr(id3),
|
||||
Status: influxdb.Active,
|
||||
CRUDLog: influxdb.CRUDLog{
|
||||
CreatedAt: timeGen1.Now(),
|
||||
UpdatedAt: timeGen2.Now(),
|
||||
},
|
||||
},
|
||||
AuthMethod: "basic",
|
||||
URL: "http://example.com",
|
||||
Username: influxdb.SecretField{
|
||||
Key: id1 + "-username",
|
||||
Value: strPtr("user1"),
|
||||
},
|
||||
Password: influxdb.SecretField{
|
||||
Key: id1 + "-password",
|
||||
Value: strPtr("password1"),
|
||||
},
|
||||
},
|
||||
secrets: []influxdb.SecretField{
|
||||
{
|
||||
Key: id1 + "-username",
|
||||
Value: strPtr("user1"),
|
||||
},
|
||||
{
|
||||
Key: id1 + "-password",
|
||||
Value: strPtr("password1"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "simple Telegram",
|
||||
src: &endpoint.Telegram{
|
||||
Base: endpoint.Base{
|
||||
ID: influxTesting.MustIDBase16Ptr(id1),
|
||||
Name: "name1",
|
||||
OrgID: influxTesting.MustIDBase16Ptr(id3),
|
||||
Status: influxdb.Active,
|
||||
CRUDLog: influxdb.CRUDLog{
|
||||
CreatedAt: timeGen1.Now(),
|
||||
UpdatedAt: timeGen2.Now(),
|
||||
},
|
||||
},
|
||||
Token: influxdb.SecretField{
|
||||
Key: id1 + "-token",
|
||||
Value: strPtr("token-value"),
|
||||
},
|
||||
},
|
||||
secrets: []influxdb.SecretField{
|
||||
{
|
||||
Key: id1 + "-token",
|
||||
Value: strPtr("token-value"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
secretFields := c.src.SecretFields()
|
||||
if diff := cmp.Diff(c.secrets, secretFields); diff != "" {
|
||||
t.Errorf("failed %s, NotificationEndpoint are different -got/+want\ndiff %s", c.name, diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func strPtr(s string) *string {
|
||||
ss := new(string)
|
||||
*ss = s
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
package endpoint
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/influxdata/influxdb/v2"
|
||||
)
|
||||
|
||||
var _ influxdb.NotificationEndpoint = &Telegram{}
|
||||
|
||||
const telegramTokenSuffix = "-token"
|
||||
|
||||
// Telegram is the notification endpoint config of telegram.
|
||||
type Telegram struct {
|
||||
Base
|
||||
// Token is the telegram bot token, see https://core.telegram.org/bots#creating-a-new-bot
|
||||
Token influxdb.SecretField `json:"token"`
|
||||
// Channel is an ID of the telegram channel, see https://core.telegram.org/bots/api#sendmessage
|
||||
Channel string `json:"channel"`
|
||||
}
|
||||
|
||||
// BackfillSecretKeys fill back the secret field key during the unmarshalling
|
||||
// if value of that secret field is not nil.
|
||||
func (s *Telegram) BackfillSecretKeys() {
|
||||
if s.Token.Key == "" && s.Token.Value != nil {
|
||||
s.Token.Key = s.idStr() + telegramTokenSuffix
|
||||
}
|
||||
}
|
||||
|
||||
// SecretFields return available secret fields.
|
||||
func (s Telegram) SecretFields() []influxdb.SecretField {
|
||||
arr := []influxdb.SecretField{}
|
||||
if s.Token.Key != "" {
|
||||
arr = append(arr, s.Token)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
// Valid returns error if some configuration is invalid
|
||||
func (s Telegram) Valid() error {
|
||||
if err := s.Base.valid(); err != nil {
|
||||
return err
|
||||
}
|
||||
if s.Token.Key == "" {
|
||||
return &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: "empty telegram bot token",
|
||||
}
|
||||
}
|
||||
if s.Channel == "" {
|
||||
return &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: "empty telegram channel",
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implement json.Marshaler interface.
|
||||
func (s Telegram) MarshalJSON() ([]byte, error) {
|
||||
type telegramAlias Telegram
|
||||
return json.Marshal(
|
||||
struct {
|
||||
telegramAlias
|
||||
Type string `json:"type"`
|
||||
}{
|
||||
telegramAlias: telegramAlias(s),
|
||||
Type: s.Type(),
|
||||
})
|
||||
}
|
||||
|
||||
// Type returns the type.
|
||||
func (s Telegram) Type() string {
|
||||
return TelegramType
|
||||
}
|
|
@ -16,6 +16,7 @@ var typeToRule = map[string](func() influxdb.NotificationRule){
|
|||
"slack": func() influxdb.NotificationRule { return &Slack{} },
|
||||
"pagerduty": func() influxdb.NotificationRule { return &PagerDuty{} },
|
||||
"http": func() influxdb.NotificationRule { return &HTTP{} },
|
||||
"telegram": func() influxdb.NotificationRule { return &Telegram{} },
|
||||
}
|
||||
|
||||
// UnmarshalJSON will convert
|
||||
|
|
|
@ -321,6 +321,41 @@ func TestJSON(t *testing.T) {
|
|||
MessageTemplate: "msg1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "simple telegram",
|
||||
src: &rule.Telegram{
|
||||
Base: rule.Base{
|
||||
ID: influxTesting.MustIDBase16(id1),
|
||||
OwnerID: influxTesting.MustIDBase16(id2),
|
||||
Name: "name1",
|
||||
OrgID: influxTesting.MustIDBase16(id3),
|
||||
RunbookLink: "runbooklink1",
|
||||
SleepUntil: &time3,
|
||||
Every: mustDuration("1h"),
|
||||
TagRules: []notification.TagRule{
|
||||
{
|
||||
Tag: influxdb.Tag{
|
||||
Key: "k1",
|
||||
Value: "v1",
|
||||
},
|
||||
Operator: influxdb.NotEqual,
|
||||
},
|
||||
{
|
||||
Tag: influxdb.Tag{
|
||||
Key: "k2",
|
||||
Value: "v2",
|
||||
},
|
||||
Operator: influxdb.RegexEqual,
|
||||
},
|
||||
},
|
||||
CRUDLog: influxdb.CRUDLog{
|
||||
CreatedAt: timeGen1.Now(),
|
||||
UpdatedAt: timeGen2.Now(),
|
||||
},
|
||||
},
|
||||
MessageTemplate: "blah",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
b, err := json.Marshal(c.src)
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
package rule
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/influxdata/flux/ast"
|
||||
"github.com/influxdata/influxdb/v2"
|
||||
"github.com/influxdata/influxdb/v2/notification/endpoint"
|
||||
"github.com/influxdata/influxdb/v2/notification/flux"
|
||||
)
|
||||
|
||||
// Telegram is the notification rule config of telegram.
|
||||
type Telegram struct {
|
||||
Base
|
||||
MessageTemplate string `json:"messageTemplate"`
|
||||
ParseMode string `json:"parseMode"`
|
||||
DisableWebPagePreview bool `json:"disableWebPagePreview"`
|
||||
}
|
||||
|
||||
// GenerateFlux generates a flux script for the telegram notification rule.
|
||||
func (s *Telegram) GenerateFlux(e influxdb.NotificationEndpoint) (string, error) {
|
||||
telegramEndpoint, ok := e.(*endpoint.Telegram)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("endpoint provided is a %s, not a Telegram endpoint", e.Type())
|
||||
}
|
||||
p, err := s.GenerateFluxAST(telegramEndpoint)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return ast.Format(p), nil
|
||||
}
|
||||
|
||||
// GenerateFluxAST generates a flux AST for the telegram notification rule.
|
||||
func (s *Telegram) GenerateFluxAST(e *endpoint.Telegram) (*ast.Package, error) {
|
||||
f := flux.File(
|
||||
s.Name,
|
||||
flux.Imports("influxdata/influxdb/monitor", "contrib/sranka/telegram", "influxdata/influxdb/secrets", "experimental"),
|
||||
s.generateFluxASTBody(e),
|
||||
)
|
||||
return &ast.Package{Package: "main", Files: []*ast.File{f}}, nil
|
||||
}
|
||||
|
||||
func (s *Telegram) generateFluxASTBody(e *endpoint.Telegram) []ast.Statement {
|
||||
var statements []ast.Statement
|
||||
statements = append(statements, s.generateTaskOption())
|
||||
if e.Token.Key != "" {
|
||||
statements = append(statements, s.generateFluxASTSecrets(e))
|
||||
}
|
||||
statements = append(statements, s.generateFluxASTEndpoint(e))
|
||||
statements = append(statements, s.generateFluxASTNotificationDefinition(e))
|
||||
statements = append(statements, s.generateFluxASTStatuses())
|
||||
statements = append(statements, s.generateLevelChecks()...)
|
||||
statements = append(statements, s.generateFluxASTNotifyPipe(e))
|
||||
|
||||
return statements
|
||||
}
|
||||
|
||||
func (s *Telegram) generateFluxASTSecrets(e *endpoint.Telegram) ast.Statement {
|
||||
call := flux.Call(flux.Member("secrets", "get"), flux.Object(flux.Property("key", flux.String(e.Token.Key))))
|
||||
|
||||
return flux.DefineVariable("telegram_secret", call)
|
||||
}
|
||||
|
||||
func (s *Telegram) generateFluxASTEndpoint(e *endpoint.Telegram) ast.Statement {
|
||||
props := []*ast.Property{}
|
||||
if e.Token.Key != "" {
|
||||
props = append(props, flux.Property("token", flux.Identifier("telegram_secret")))
|
||||
}
|
||||
if s.ParseMode != "" {
|
||||
props = append(props, flux.Property("parseMode", flux.String(s.ParseMode)))
|
||||
}
|
||||
props = append(props, flux.Property("disableWebPagePreview", flux.Bool(s.DisableWebPagePreview)))
|
||||
call := flux.Call(flux.Member("telegram", "endpoint"), flux.Object(props...))
|
||||
|
||||
return flux.DefineVariable("telegram_endpoint", call)
|
||||
}
|
||||
|
||||
func (s *Telegram) generateFluxASTNotifyPipe(e *endpoint.Telegram) ast.Statement {
|
||||
endpointProps := []*ast.Property{}
|
||||
endpointProps = append(endpointProps, flux.Property("channel", flux.String(e.Channel)))
|
||||
endpointProps = append(endpointProps, flux.Property("text", flux.String(s.MessageTemplate)))
|
||||
endpointProps = append(endpointProps, flux.Property("silent", s.generateSilent()))
|
||||
endpointFn := flux.Function(flux.FunctionParams("r"), flux.Object(endpointProps...))
|
||||
|
||||
props := []*ast.Property{}
|
||||
props = append(props, flux.Property("data", flux.Identifier("notification")))
|
||||
props = append(props, flux.Property("endpoint",
|
||||
flux.Call(flux.Identifier("telegram_endpoint"), flux.Object(flux.Property("mapFn", endpointFn)))))
|
||||
|
||||
call := flux.Call(flux.Member("monitor", "notify"), flux.Object(props...))
|
||||
|
||||
return flux.ExpressionStatement(flux.Pipe(flux.Identifier("all_statuses"), call))
|
||||
}
|
||||
|
||||
func (s *Telegram) generateSilent() ast.Expression {
|
||||
level := flux.Member("r", "_level")
|
||||
return flux.If(
|
||||
flux.Equal(level, flux.String("crit")),
|
||||
flux.Bool(true),
|
||||
flux.If(
|
||||
flux.Equal(level, flux.String("warn")),
|
||||
flux.Bool(true),
|
||||
flux.Bool(false),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
type telegramAlias Telegram
|
||||
|
||||
// MarshalJSON implement json.Marshaler interface.
|
||||
func (s Telegram) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(
|
||||
struct {
|
||||
telegramAlias
|
||||
Type string `json:"type"`
|
||||
}{
|
||||
telegramAlias: telegramAlias(s),
|
||||
Type: s.Type(),
|
||||
})
|
||||
}
|
||||
|
||||
// Valid returns where the config is valid.
|
||||
func (s Telegram) Valid() error {
|
||||
if err := s.Base.valid(); err != nil {
|
||||
return err
|
||||
}
|
||||
if s.MessageTemplate == "" {
|
||||
return &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: "Telegram MessageTemplate is invalid",
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type returns the type of the rule config.
|
||||
func (s Telegram) Type() string {
|
||||
return "telegram"
|
||||
}
|
|
@ -0,0 +1,308 @@
|
|||
package rule_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/andreyvit/diff"
|
||||
"github.com/influxdata/influxdb/v2"
|
||||
"github.com/influxdata/influxdb/v2/notification"
|
||||
"github.com/influxdata/influxdb/v2/notification/endpoint"
|
||||
"github.com/influxdata/influxdb/v2/notification/rule"
|
||||
influxTesting "github.com/influxdata/influxdb/v2/testing"
|
||||
)
|
||||
|
||||
var _ influxdb.NotificationRule = &rule.Telegram{}
|
||||
|
||||
func TestTelegram_GenerateFlux(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
rule *rule.Telegram
|
||||
endpoint influxdb.NotificationEndpoint
|
||||
script string
|
||||
}{
|
||||
{
|
||||
name: "incompatible with endpoint",
|
||||
endpoint: &endpoint.Slack{
|
||||
Base: endpoint.Base{
|
||||
ID: idPtr(3),
|
||||
Name: "foo",
|
||||
},
|
||||
URL: "http://whatever",
|
||||
},
|
||||
rule: &rule.Telegram{
|
||||
MessageTemplate: "blah",
|
||||
Base: rule.Base{
|
||||
ID: 1,
|
||||
EndpointID: 3,
|
||||
Name: "foo",
|
||||
Every: mustDuration("1h"),
|
||||
StatusRules: []notification.StatusRule{
|
||||
{
|
||||
CurrentLevel: notification.Critical,
|
||||
},
|
||||
},
|
||||
TagRules: []notification.TagRule{
|
||||
{
|
||||
Tag: influxdb.Tag{
|
||||
Key: "foo",
|
||||
Value: "bar",
|
||||
},
|
||||
Operator: influxdb.Equal,
|
||||
},
|
||||
{
|
||||
Tag: influxdb.Tag{
|
||||
Key: "baz",
|
||||
Value: "bang",
|
||||
},
|
||||
Operator: influxdb.Equal,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
script: "", //no script generater, because of incompatible endpoint
|
||||
},
|
||||
{
|
||||
name: "notify on crit",
|
||||
endpoint: &endpoint.Telegram{
|
||||
Base: endpoint.Base{
|
||||
ID: idPtr(3),
|
||||
Name: "foo",
|
||||
},
|
||||
Token: influxdb.SecretField{Key: "3-key"},
|
||||
Channel: "-12345",
|
||||
},
|
||||
rule: &rule.Telegram{
|
||||
MessageTemplate: "blah",
|
||||
Base: rule.Base{
|
||||
ID: 1,
|
||||
EndpointID: 3,
|
||||
Name: "foo",
|
||||
Every: mustDuration("1h"),
|
||||
StatusRules: []notification.StatusRule{
|
||||
{
|
||||
CurrentLevel: notification.Critical,
|
||||
},
|
||||
},
|
||||
TagRules: []notification.TagRule{
|
||||
{
|
||||
Tag: influxdb.Tag{
|
||||
Key: "foo",
|
||||
Value: "bar",
|
||||
},
|
||||
Operator: influxdb.Equal,
|
||||
},
|
||||
{
|
||||
Tag: influxdb.Tag{
|
||||
Key: "baz",
|
||||
Value: "bang",
|
||||
},
|
||||
Operator: influxdb.Equal,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
script: `package main
|
||||
// foo
|
||||
import "influxdata/influxdb/monitor"
|
||||
import "contrib/sranka/telegram"
|
||||
import "influxdata/influxdb/secrets"
|
||||
import "experimental"
|
||||
|
||||
option task = {name: "foo", every: 1h}
|
||||
|
||||
telegram_secret = secrets["get"](key: "3-key")
|
||||
telegram_endpoint = telegram["endpoint"](token: telegram_secret, disableWebPagePreview: false)
|
||||
notification = {
|
||||
_notification_rule_id: "0000000000000001",
|
||||
_notification_rule_name: "foo",
|
||||
_notification_endpoint_id: "0000000000000003",
|
||||
_notification_endpoint_name: "foo",
|
||||
}
|
||||
statuses = monitor["from"](start: -2h, fn: (r) =>
|
||||
(r["foo"] == "bar" and r["baz"] == "bang"))
|
||||
crit = statuses
|
||||
|> filter(fn: (r) =>
|
||||
(r["_level"] == "crit"))
|
||||
all_statuses = crit
|
||||
|> filter(fn: (r) =>
|
||||
(r["_time"] > experimental["subDuration"](from: now(), d: 1h)))
|
||||
|
||||
all_statuses
|
||||
|> monitor["notify"](data: notification, endpoint: telegram_endpoint(mapFn: (r) =>
|
||||
({channel: "-12345", text: "blah", silent: if r["_level"] == "crit" then true else if r["_level"] == "warn" then true else false})))`,
|
||||
},
|
||||
{
|
||||
name: "with DisableWebPagePreview and ParseMode",
|
||||
endpoint: &endpoint.Telegram{
|
||||
Base: endpoint.Base{
|
||||
ID: idPtr(3),
|
||||
Name: "foo",
|
||||
},
|
||||
Token: influxdb.SecretField{Key: "3-key"},
|
||||
Channel: "-12345",
|
||||
},
|
||||
rule: &rule.Telegram{
|
||||
MessageTemplate: "blah",
|
||||
DisableWebPagePreview: true,
|
||||
ParseMode: "HTML",
|
||||
Base: rule.Base{
|
||||
ID: 1,
|
||||
EndpointID: 3,
|
||||
Name: "foo",
|
||||
Every: mustDuration("1h"),
|
||||
StatusRules: []notification.StatusRule{
|
||||
{
|
||||
CurrentLevel: notification.Any,
|
||||
},
|
||||
},
|
||||
TagRules: []notification.TagRule{
|
||||
{
|
||||
Tag: influxdb.Tag{
|
||||
Key: "foo",
|
||||
Value: "bar",
|
||||
},
|
||||
Operator: influxdb.Equal,
|
||||
},
|
||||
{
|
||||
Tag: influxdb.Tag{
|
||||
Key: "baz",
|
||||
Value: "bang",
|
||||
},
|
||||
Operator: influxdb.Equal,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
script: `package main
|
||||
// foo
|
||||
import "influxdata/influxdb/monitor"
|
||||
import "contrib/sranka/telegram"
|
||||
import "influxdata/influxdb/secrets"
|
||||
import "experimental"
|
||||
|
||||
option task = {name: "foo", every: 1h}
|
||||
|
||||
telegram_secret = secrets["get"](key: "3-key")
|
||||
telegram_endpoint = telegram["endpoint"](token: telegram_secret, parseMode: "HTML", disableWebPagePreview: true)
|
||||
notification = {
|
||||
_notification_rule_id: "0000000000000001",
|
||||
_notification_rule_name: "foo",
|
||||
_notification_endpoint_id: "0000000000000003",
|
||||
_notification_endpoint_name: "foo",
|
||||
}
|
||||
statuses = monitor["from"](start: -2h, fn: (r) =>
|
||||
(r["foo"] == "bar" and r["baz"] == "bang"))
|
||||
any = statuses
|
||||
|> filter(fn: (r) =>
|
||||
(true))
|
||||
all_statuses = any
|
||||
|> filter(fn: (r) =>
|
||||
(r["_time"] > experimental["subDuration"](from: now(), d: 1h)))
|
||||
|
||||
all_statuses
|
||||
|> monitor["notify"](data: notification, endpoint: telegram_endpoint(mapFn: (r) =>
|
||||
({channel: "-12345", text: "blah", silent: if r["_level"] == "crit" then true else if r["_level"] == "warn" then true else false})))`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
script, err := tt.rule.GenerateFlux(tt.endpoint)
|
||||
if err != nil {
|
||||
if script != "" {
|
||||
t.Errorf("Failed to generate flux: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if got, want := script, tt.script; got != want {
|
||||
t.Errorf("\n\nStrings do not match:\n\n%s", diff.LineDiff(got, want))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTelegram_Valid(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
rule *rule.Telegram
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "valid template",
|
||||
rule: &rule.Telegram{
|
||||
MessageTemplate: "blah",
|
||||
Base: rule.Base{
|
||||
ID: 1,
|
||||
EndpointID: 3,
|
||||
OwnerID: 4,
|
||||
OrgID: 5,
|
||||
Name: "foo",
|
||||
Every: mustDuration("1h"),
|
||||
StatusRules: []notification.StatusRule{
|
||||
{
|
||||
CurrentLevel: notification.Critical,
|
||||
},
|
||||
},
|
||||
TagRules: []notification.TagRule{},
|
||||
},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "missing MessageTemplate",
|
||||
rule: &rule.Telegram{
|
||||
MessageTemplate: "",
|
||||
Base: rule.Base{
|
||||
ID: 1,
|
||||
EndpointID: 3,
|
||||
OwnerID: 4,
|
||||
OrgID: 5,
|
||||
Name: "foo",
|
||||
Every: mustDuration("1h"),
|
||||
StatusRules: []notification.StatusRule{
|
||||
{
|
||||
CurrentLevel: notification.Critical,
|
||||
},
|
||||
},
|
||||
TagRules: []notification.TagRule{},
|
||||
},
|
||||
},
|
||||
err: &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: "Telegram MessageTemplate is invalid",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing EndpointID",
|
||||
rule: &rule.Telegram{
|
||||
MessageTemplate: "",
|
||||
Base: rule.Base{
|
||||
ID: 1,
|
||||
// EndpointID: 3,
|
||||
OwnerID: 4,
|
||||
OrgID: 5,
|
||||
Name: "foo",
|
||||
Every: mustDuration("1h"),
|
||||
StatusRules: []notification.StatusRule{
|
||||
{
|
||||
CurrentLevel: notification.Critical,
|
||||
},
|
||||
},
|
||||
TagRules: []notification.TagRule{},
|
||||
},
|
||||
},
|
||||
err: &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: "Notification Rule EndpointID is invalid",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
got := c.rule.Valid()
|
||||
influxTesting.ErrorsEqual(t, got, c.err)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
|
@ -13,7 +13,7 @@ import (
|
|||
const CompilerType = "influxql"
|
||||
|
||||
// AddCompilerMappings adds the influxql specific compiler mappings.
|
||||
func AddCompilerMappings(mappings flux.CompilerMappings, dbrpMappingSvc platform.DBRPMappingService) error {
|
||||
func AddCompilerMappings(mappings flux.CompilerMappings, dbrpMappingSvc platform.DBRPMappingServiceV2) error {
|
||||
return mappings.Add(CompilerType, func() flux.Compiler {
|
||||
return NewCompiler(dbrpMappingSvc)
|
||||
})
|
||||
|
@ -30,12 +30,12 @@ type Compiler struct {
|
|||
|
||||
logicalPlannerOptions []plan.LogicalOption
|
||||
|
||||
dbrpMappingSvc platform.DBRPMappingService
|
||||
dbrpMappingSvc platform.DBRPMappingServiceV2
|
||||
}
|
||||
|
||||
var _ flux.Compiler = &Compiler{}
|
||||
|
||||
func NewCompiler(dbrpMappingSvc platform.DBRPMappingService) *Compiler {
|
||||
func NewCompiler(dbrpMappingSvc platform.DBRPMappingServiceV2) *Compiler {
|
||||
return &Compiler{
|
||||
dbrpMappingSvc: dbrpMappingSvc,
|
||||
}
|
||||
|
|
|
@ -27,25 +27,21 @@ import (
|
|||
|
||||
const generatedInfluxQLDataDir = "testdata"
|
||||
|
||||
var dbrpMappingSvcE2E = mock.NewDBRPMappingService()
|
||||
var dbrpMappingSvcE2E = &mock.DBRPMappingServiceV2{}
|
||||
|
||||
func init() {
|
||||
mapping := platform.DBRPMapping{
|
||||
Cluster: "cluster",
|
||||
mapping := platform.DBRPMappingV2{
|
||||
Database: "db0",
|
||||
RetentionPolicy: "autogen",
|
||||
Default: true,
|
||||
OrganizationID: platformtesting.MustIDBase16("cadecadecadecade"),
|
||||
BucketID: platformtesting.MustIDBase16("da7aba5e5eedca5e"),
|
||||
}
|
||||
dbrpMappingSvcE2E.FindByFn = func(ctx context.Context, cluster string, db string, rp string) (*platform.DBRPMapping, error) {
|
||||
dbrpMappingSvcE2E.FindByIDFn = func(ctx context.Context, orgID, id platform.ID) (*platform.DBRPMappingV2, error) {
|
||||
return &mapping, nil
|
||||
}
|
||||
dbrpMappingSvcE2E.FindFn = func(ctx context.Context, filter platform.DBRPMappingFilter) (*platform.DBRPMapping, error) {
|
||||
return &mapping, nil
|
||||
}
|
||||
dbrpMappingSvcE2E.FindManyFn = func(ctx context.Context, filter platform.DBRPMappingFilter, opt ...platform.FindOptions) ([]*platform.DBRPMapping, int, error) {
|
||||
return []*platform.DBRPMapping{&mapping}, 1, nil
|
||||
dbrpMappingSvcE2E.FindManyFn = func(ctx context.Context, filter platform.DBRPMappingFilterV2, opt ...platform.FindOptions) ([]*platform.DBRPMappingV2, int, error) {
|
||||
return []*platform.DBRPMappingV2{&mapping}, 1, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,46 +16,35 @@ import (
|
|||
platformtesting "github.com/influxdata/influxdb/v2/testing"
|
||||
)
|
||||
|
||||
var dbrpMappingSvc = mock.NewDBRPMappingService()
|
||||
var dbrpMappingSvc = &mock.DBRPMappingServiceV2{}
|
||||
var organizationID platform.ID
|
||||
var bucketID platform.ID
|
||||
var altBucketID platform.ID
|
||||
|
||||
func init() {
|
||||
mapping := platform.DBRPMapping{
|
||||
Cluster: "cluster",
|
||||
mapping := platform.DBRPMappingV2{
|
||||
Database: "db0",
|
||||
RetentionPolicy: "autogen",
|
||||
Default: true,
|
||||
OrganizationID: organizationID,
|
||||
BucketID: bucketID,
|
||||
}
|
||||
altMapping := platform.DBRPMapping{
|
||||
Cluster: "cluster",
|
||||
altMapping := platform.DBRPMappingV2{
|
||||
Database: "db0",
|
||||
RetentionPolicy: "autogen",
|
||||
Default: true,
|
||||
OrganizationID: organizationID,
|
||||
BucketID: altBucketID,
|
||||
}
|
||||
dbrpMappingSvc.FindByFn = func(ctx context.Context, cluster string, db string, rp string) (*platform.DBRPMapping, error) {
|
||||
if rp == "alternate" {
|
||||
return &altMapping, nil
|
||||
}
|
||||
dbrpMappingSvc.FindByIDFn = func(ctx context.Context, orgID, id platform.ID) (*platform.DBRPMappingV2, error) {
|
||||
return &mapping, nil
|
||||
}
|
||||
dbrpMappingSvc.FindFn = func(ctx context.Context, filter platform.DBRPMappingFilter) (*platform.DBRPMapping, error) {
|
||||
if filter.RetentionPolicy != nil && *filter.RetentionPolicy == "alternate" {
|
||||
return &altMapping, nil
|
||||
}
|
||||
return &mapping, nil
|
||||
}
|
||||
dbrpMappingSvc.FindManyFn = func(ctx context.Context, filter platform.DBRPMappingFilter, opt ...platform.FindOptions) ([]*platform.DBRPMapping, int, error) {
|
||||
dbrpMappingSvc.FindManyFn = func(ctx context.Context, filter platform.DBRPMappingFilterV2, opt ...platform.FindOptions) ([]*platform.DBRPMappingV2, int, error) {
|
||||
m := &mapping
|
||||
if filter.RetentionPolicy != nil && *filter.RetentionPolicy == "alternate" {
|
||||
m = &altMapping
|
||||
}
|
||||
return []*platform.DBRPMapping{m}, 1, nil
|
||||
return []*platform.DBRPMappingV2{m}, 1, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,14 +16,14 @@ import (
|
|||
// Transpiler converts InfluxQL queries into a query spec.
|
||||
type Transpiler struct {
|
||||
Config *Config
|
||||
dbrpMappingSvc influxdb.DBRPMappingService
|
||||
dbrpMappingSvc influxdb.DBRPMappingServiceV2
|
||||
}
|
||||
|
||||
func NewTranspiler(dbrpMappingSvc influxdb.DBRPMappingService) *Transpiler {
|
||||
func NewTranspiler(dbrpMappingSvc influxdb.DBRPMappingServiceV2) *Transpiler {
|
||||
return NewTranspilerWithConfig(dbrpMappingSvc, Config{})
|
||||
}
|
||||
|
||||
func NewTranspilerWithConfig(dbrpMappingSvc influxdb.DBRPMappingService, cfg Config) *Transpiler {
|
||||
func NewTranspilerWithConfig(dbrpMappingSvc influxdb.DBRPMappingServiceV2, cfg Config) *Transpiler {
|
||||
return &Transpiler{
|
||||
Config: &cfg,
|
||||
dbrpMappingSvc: dbrpMappingSvc,
|
||||
|
@ -56,10 +56,10 @@ type transpilerState struct {
|
|||
config Config
|
||||
file *ast.File
|
||||
assignments map[string]ast.Expression
|
||||
dbrpMappingSvc influxdb.DBRPMappingService
|
||||
dbrpMappingSvc influxdb.DBRPMappingServiceV2
|
||||
}
|
||||
|
||||
func newTranspilerState(dbrpMappingSvc influxdb.DBRPMappingService, config *Config) *transpilerState {
|
||||
func newTranspilerState(dbrpMappingSvc influxdb.DBRPMappingServiceV2, config *Config) *transpilerState {
|
||||
state := &transpilerState{
|
||||
file: &ast.File{
|
||||
Package: &ast.PackageClause{
|
||||
|
@ -695,8 +695,7 @@ func (t *transpilerState) from(m *influxql.Measurement) (ast.Expression, error)
|
|||
}
|
||||
}
|
||||
|
||||
var filter influxdb.DBRPMappingFilter
|
||||
filter.Cluster = &t.config.Cluster
|
||||
var filter influxdb.DBRPMappingFilterV2
|
||||
if db != "" {
|
||||
filter.Database = &db
|
||||
}
|
||||
|
@ -705,8 +704,8 @@ func (t *transpilerState) from(m *influxql.Measurement) (ast.Expression, error)
|
|||
}
|
||||
defaultRP := rp == ""
|
||||
filter.Default = &defaultRP
|
||||
mapping, err := t.dbrpMappingSvc.Find(context.TODO(), filter)
|
||||
if err != nil {
|
||||
mappings, _, err := t.dbrpMappingSvc.FindMany(context.TODO(), filter)
|
||||
if err != nil || len(mappings) == 0 {
|
||||
if !t.config.FallbackToDBRP {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -735,7 +734,7 @@ func (t *transpilerState) from(m *influxql.Measurement) (ast.Expression, error)
|
|||
Name: "bucketID",
|
||||
},
|
||||
Value: &ast.StringLiteral{
|
||||
Value: mapping.BucketID.String(),
|
||||
Value: mappings[0].BucketID.String(),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -13,25 +13,21 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var dbrpMappingSvc = mock.NewDBRPMappingService()
|
||||
var dbrpMappingSvc = &mock.DBRPMappingServiceV2{}
|
||||
|
||||
func init() {
|
||||
mapping := platform.DBRPMapping{
|
||||
Cluster: "cluster",
|
||||
mapping := platform.DBRPMappingV2{
|
||||
Database: "db0",
|
||||
RetentionPolicy: "autogen",
|
||||
Default: true,
|
||||
OrganizationID: platformtesting.MustIDBase16("aaaaaaaaaaaaaaaa"),
|
||||
BucketID: platformtesting.MustIDBase16("bbbbbbbbbbbbbbbb"),
|
||||
}
|
||||
dbrpMappingSvc.FindByFn = func(ctx context.Context, cluster string, db string, rp string) (*platform.DBRPMapping, error) {
|
||||
dbrpMappingSvc.FindByIDFn = func(ctx context.Context, orgID, id platform.ID) (*platform.DBRPMappingV2, error) {
|
||||
return &mapping, nil
|
||||
}
|
||||
dbrpMappingSvc.FindFn = func(ctx context.Context, filter platform.DBRPMappingFilter) (*platform.DBRPMapping, error) {
|
||||
return &mapping, nil
|
||||
}
|
||||
dbrpMappingSvc.FindManyFn = func(ctx context.Context, filter platform.DBRPMappingFilter, opt ...platform.FindOptions) ([]*platform.DBRPMapping, int, error) {
|
||||
return []*platform.DBRPMapping{&mapping}, 1, nil
|
||||
dbrpMappingSvc.FindManyFn = func(ctx context.Context, filter platform.DBRPMappingFilterV2, opt ...platform.FindOptions) ([]*platform.DBRPMappingV2, int, error) {
|
||||
return []*platform.DBRPMappingV2{&mapping}, 1, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -389,8 +389,8 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH
|
|||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/influxdata/cron v0.0.0-20191203200038-ded12750aac6 h1:OtjKkeWDjUbyMi82C7XXy7Tvm2LXMwiBBXyFIGNPaGA=
|
||||
github.com/influxdata/cron v0.0.0-20191203200038-ded12750aac6/go.mod h1:XabtPPW2qsCg0tl+kjaPU+cFS+CjQXEXbT1VJvHT4og=
|
||||
github.com/influxdata/flux v0.77.1 h1:cOC/Kash0jSyeFnYdTEGicHvJnm+4BtazwglSpOIKIM=
|
||||
github.com/influxdata/flux v0.77.1/go.mod h1:sAAIEgQTlTpsXCUQ49ymoRsKqraPzIb7F3paT72/lE0=
|
||||
github.com/influxdata/flux v0.80.0 h1:lKZyJNgJf/oCzSUknAqAEq0OwwV6WUU+ZNKFUx2TaBQ=
|
||||
github.com/influxdata/flux v0.80.0/go.mod h1:sAAIEgQTlTpsXCUQ49ymoRsKqraPzIb7F3paT72/lE0=
|
||||
github.com/influxdata/httprouter v1.3.1-0.20191122104820-ee83e2772f69 h1:WQsmW0fXO4ZE/lFGIE84G6rIV5SJN3P3sjIXAP1a8eU=
|
||||
github.com/influxdata/httprouter v1.3.1-0.20191122104820-ee83e2772f69/go.mod h1:pwymjR6SrP3gD3pRj9RJwdl1j5s3doEEV8gS4X9qSzA=
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
|
||||
|
|
|
@ -69,7 +69,7 @@ func init() {
|
|||
type DatabasesDecoder struct {
|
||||
orgID platform.ID
|
||||
deps *DatabasesDependencies
|
||||
databases []*platform.DBRPMapping
|
||||
databases []*platform.DBRPMappingV2
|
||||
alloc *memory.Allocator
|
||||
}
|
||||
|
||||
|
@ -78,7 +78,7 @@ func (bd *DatabasesDecoder) Connect(ctx context.Context) error {
|
|||
}
|
||||
|
||||
func (bd *DatabasesDecoder) Fetch(ctx context.Context) (bool, error) {
|
||||
b, _, err := bd.deps.DBRP.FindMany(ctx, platform.DBRPMappingFilter{})
|
||||
b, _, err := bd.deps.DBRP.FindMany(ctx, platform.DBRPMappingFilterV2{})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ func (bd *DatabasesDecoder) Fetch(ctx context.Context) (bool, error) {
|
|||
|
||||
func (bd *DatabasesDecoder) Decode(ctx context.Context) (flux.Table, error) {
|
||||
type databaseInfo struct {
|
||||
*platform.DBRPMapping
|
||||
*platform.DBRPMappingV2
|
||||
RetentionPeriod time.Duration
|
||||
}
|
||||
|
||||
|
@ -103,7 +103,7 @@ func (bd *DatabasesDecoder) Decode(ctx context.Context) (flux.Table, error) {
|
|||
return nil, err
|
||||
}
|
||||
databases = append(databases, databaseInfo{
|
||||
DBRPMapping: db,
|
||||
DBRPMappingV2: db,
|
||||
RetentionPeriod: bucket.RetentionPeriod,
|
||||
})
|
||||
}
|
||||
|
@ -198,7 +198,7 @@ type key int
|
|||
const dependenciesKey key = iota
|
||||
|
||||
type DatabasesDependencies struct {
|
||||
DBRP platform.DBRPMappingService
|
||||
DBRP platform.DBRPMappingServiceV2
|
||||
BucketLookup platform.BucketService
|
||||
}
|
||||
|
||||
|
|
|
@ -96,6 +96,10 @@ var FluxEndToEndSkipList = map[string]map[string]string{
|
|||
"join": "unbounded test",
|
||||
"alignTime": "unbounded test",
|
||||
},
|
||||
"experimental/array": {
|
||||
"from": "test not meant to be consumed by influxdb",
|
||||
"from_group": "test not meant to be consumed by influxdb",
|
||||
},
|
||||
"experimental/geo": {
|
||||
"filterRowsNotStrict": "tableFind does not work in e2e tests: https://github.com/influxdata/influxdb/issues/13975",
|
||||
"filterRowsStrict": "tableFind does not work in e2e tests: https://github.com/influxdata/influxdb/issues/13975",
|
||||
|
|
|
@ -41,7 +41,7 @@ func (s *UserResourceMappingClient) FindUserResourceMappings(ctx context.Context
|
|||
urs[k] = &influxdb.UserResourceMapping{
|
||||
ResourceID: f.ResourceID,
|
||||
ResourceType: f.ResourceType,
|
||||
UserID: item.ID,
|
||||
UserID: item.User.ID,
|
||||
UserType: item.Role,
|
||||
}
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ func (s *SpecificURMSvc) FindUserResourceMappings(ctx context.Context, f influxd
|
|||
urs[k] = &influxdb.UserResourceMapping{
|
||||
ResourceID: f.ResourceID,
|
||||
ResourceType: f.ResourceType,
|
||||
UserID: item.ID,
|
||||
UserID: item.User.ID,
|
||||
UserType: item.Role,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ func (h *urmHandler) getURMsByType(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
users := make([]influxdb.User, 0, len(mappings))
|
||||
users := make([]*influxdb.User, 0, len(mappings))
|
||||
for _, m := range mappings {
|
||||
if m.MappingType == influxdb.OrgMappingType {
|
||||
continue
|
||||
|
@ -74,7 +74,7 @@ func (h *urmHandler) getURMsByType(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
users = append(users, *user)
|
||||
users = append(users, user)
|
||||
}
|
||||
h.log.Debug("Members/owners retrieved", zap.String("users", fmt.Sprint(users)))
|
||||
|
||||
|
@ -134,7 +134,7 @@ func (h *urmHandler) postURMByType(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
h.log.Debug("Member/owner created", zap.String("mapping", fmt.Sprint(mapping)))
|
||||
|
||||
h.api.Respond(w, r, http.StatusCreated, newResourceUserResponse(*user, userType))
|
||||
h.api.Respond(w, r, http.StatusCreated, newResourceUserResponse(user, userType))
|
||||
}
|
||||
|
||||
type postRequest struct {
|
||||
|
@ -229,27 +229,15 @@ func (h *urmHandler) decodeDeleteRequest(ctx context.Context, r *http.Request) (
|
|||
}, nil
|
||||
}
|
||||
|
||||
type URMUserResponse struct {
|
||||
Links map[string]string `json:"links"`
|
||||
ID influxdb.ID `json:"id,omitempty"`
|
||||
Status influxdb.Status `json:"status"`
|
||||
}
|
||||
|
||||
type resourceUserResponse struct {
|
||||
Role influxdb.UserType `json:"role"`
|
||||
*URMUserResponse
|
||||
*UserResponse
|
||||
}
|
||||
|
||||
func newResourceUserResponse(u influxdb.User, userType influxdb.UserType) *resourceUserResponse {
|
||||
func newResourceUserResponse(u *influxdb.User, userType influxdb.UserType) *resourceUserResponse {
|
||||
return &resourceUserResponse{
|
||||
Role: userType,
|
||||
URMUserResponse: &URMUserResponse{
|
||||
Links: map[string]string{
|
||||
"self": fmt.Sprintf("/api/v2/users/%s", u.ID),
|
||||
},
|
||||
ID: u.ID,
|
||||
Status: u.Status,
|
||||
},
|
||||
Role: userType,
|
||||
UserResponse: newUserResponse(u),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -258,7 +246,7 @@ type resourceUsersResponse struct {
|
|||
Users []*resourceUserResponse `json:"users"`
|
||||
}
|
||||
|
||||
func newResourceUsersResponse(f influxdb.UserResourceMappingFilter, users []influxdb.User) *resourceUsersResponse {
|
||||
func newResourceUsersResponse(f influxdb.UserResourceMappingFilter, users []*influxdb.User) *resourceUsersResponse {
|
||||
rs := resourceUsersResponse{
|
||||
Links: map[string]string{
|
||||
"self": fmt.Sprintf("/api/v2/%s/%s/%ss", f.ResourceType, f.ResourceID, f.UserType),
|
||||
|
|
|
@ -87,6 +87,7 @@ func TestUserResourceMappingService_GetMembersHandler(t *testing.T) {
|
|||
"self": "/api/v2/users/0000000000000001"
|
||||
},
|
||||
"id": "0000000000000001",
|
||||
"name": "user0000000000000001",
|
||||
"status": "active"
|
||||
},
|
||||
{
|
||||
|
@ -95,6 +96,7 @@ func TestUserResourceMappingService_GetMembersHandler(t *testing.T) {
|
|||
"self": "/api/v2/users/0000000000000002"
|
||||
},
|
||||
"id": "0000000000000002",
|
||||
"name": "user0000000000000002",
|
||||
"status": "active"
|
||||
}
|
||||
]
|
||||
|
@ -148,6 +150,7 @@ func TestUserResourceMappingService_GetMembersHandler(t *testing.T) {
|
|||
"self": "/api/v2/users/0000000000000001"
|
||||
},
|
||||
"id": "0000000000000001",
|
||||
"name": "user0000000000000001",
|
||||
"status": "active"
|
||||
},
|
||||
{
|
||||
|
@ -156,6 +159,7 @@ func TestUserResourceMappingService_GetMembersHandler(t *testing.T) {
|
|||
"self": "/api/v2/users/0000000000000002"
|
||||
},
|
||||
"id": "0000000000000002",
|
||||
"name": "user0000000000000002",
|
||||
"status": "active"
|
||||
}
|
||||
]
|
||||
|
@ -267,6 +271,7 @@ func TestUserResourceMappingService_PostMembersHandler(t *testing.T) {
|
|||
"self": "/api/v2/users/0000000000000001"
|
||||
},
|
||||
"id": "0000000000000001",
|
||||
"name": "user0000000000000001",
|
||||
"status": "active"
|
||||
}`,
|
||||
},
|
||||
|
@ -304,6 +309,7 @@ func TestUserResourceMappingService_PostMembersHandler(t *testing.T) {
|
|||
"self": "/api/v2/users/0000000000000002"
|
||||
},
|
||||
"id": "0000000000000002",
|
||||
"name": "user0000000000000002",
|
||||
"status": "active"
|
||||
}`,
|
||||
},
|
||||
|
|
|
@ -0,0 +1,231 @@
|
|||
describe('Community Templates', () => {
|
||||
beforeEach(() => {
|
||||
cy.flush()
|
||||
|
||||
cy.signin().then(({body}) => {
|
||||
const {
|
||||
org: {id},
|
||||
} = body
|
||||
cy.wrap(body.org).as('org')
|
||||
|
||||
cy.fixture('routes').then(({orgs}) => {
|
||||
cy.visit(`${orgs}/${id}/settings/templates`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('The browse community template button launches github', () => {
|
||||
cy.getByTestID('browse-template-button')
|
||||
.should('have.prop', 'href')
|
||||
.and(
|
||||
'equal',
|
||||
'https://github.com/influxdata/community-templates#templates'
|
||||
)
|
||||
})
|
||||
|
||||
it('The lookup template errors on invalid data', () => {
|
||||
//on empty
|
||||
cy.getByTestID('lookup-template-button').click()
|
||||
cy.getByTestID('notification-error').should('be.visible')
|
||||
|
||||
//lookup template errors on bad url
|
||||
cy.getByTestID('lookup-template-input').type('www.badURL.com')
|
||||
cy.getByTestID('lookup-template-button').click()
|
||||
cy.getByTestID('notification-error').should('be.visible')
|
||||
|
||||
//lookup template errors on bad file type
|
||||
cy.getByTestID('lookup-template-input').clear()
|
||||
cy.getByTestID('lookup-template-input').type('variables.html')
|
||||
cy.getByTestID('lookup-template-button').click()
|
||||
cy.getByTestID('notification-error').should('be.visible')
|
||||
|
||||
//lookup template errors on github folder
|
||||
cy.getByTestID('lookup-template-input').clear()
|
||||
cy.getByTestID('lookup-template-input').type(
|
||||
'https://github.com/influxdata/community-templates/tree/master/kafka'
|
||||
)
|
||||
cy.getByTestID('lookup-template-button').click()
|
||||
cy.getByTestID('notification-error').should('be.visible')
|
||||
})
|
||||
|
||||
it.skip('Can install from CLI', () => {
|
||||
//authorization is preventing this from working
|
||||
cy.exec(
|
||||
'go run ../cmd/influx apply -t eiDTSTOZ_WAgLfw9eK5_JUsVnqeIYWWBY2QHXe6KC-UneLThJBGveTMm8k6_W1cAmswzLEKJTPeqoirvHH5kQg== -f pkger/testdata/variables.yml'
|
||||
).then(result => {
|
||||
result
|
||||
})
|
||||
})
|
||||
|
||||
it('Simple Download', () => {
|
||||
//The lookup template accepts github raw link
|
||||
cy.getByTestID('lookup-template-input').type(
|
||||
'https://raw.githubusercontent.com/influxdata/community-templates/master/downsampling/dashboard.yml'
|
||||
)
|
||||
cy.getByTestID('lookup-template-button').click()
|
||||
cy.getByTestID('template-install-overlay').should('be.visible')
|
||||
|
||||
//check that with 1 resource pluralization is correct
|
||||
cy.getByTestID('template-install-title').should('contain', 'resource')
|
||||
cy.getByTestID('template-install-title').should('not.contain', 'resources')
|
||||
|
||||
//check that no resources check lead to disabled install button
|
||||
cy.getByTestID('heading-Dashboards').click()
|
||||
cy.getByTestID('templates-toggle--Downsampling Status').should('be.visible')
|
||||
cy.getByTestID('template-install-button').should('exist')
|
||||
cy.getByTestID('templates-toggle--Downsampling Status').click()
|
||||
cy.getByTestID('template-install-button').should('not.exist')
|
||||
|
||||
//and check that 0 resources pluralization is correct
|
||||
cy.getByTestID('template-install-title').should('contain', 'resources')
|
||||
})
|
||||
|
||||
describe('Opening the install overlay', () => {
|
||||
beforeEach(() => {
|
||||
//lookup normal github link
|
||||
cy.getByTestID('lookup-template-input').type(
|
||||
'https://github.com/influxdata/community-templates/blob/master/docker/docker.yml'
|
||||
)
|
||||
cy.getByTestID('lookup-template-button').click()
|
||||
cy.getByTestID('template-install-overlay').should('be.visible')
|
||||
})
|
||||
|
||||
it('Complicated Download', () => {
|
||||
//check that with multiple resources pluralization is correct
|
||||
cy.getByTestID('template-install-title').should('contain', 'resources')
|
||||
|
||||
//no uncheck of buckets
|
||||
cy.getByTestID('template-install-title').should('contain', '22')
|
||||
cy.getByTestID('heading-Buckets').click()
|
||||
cy.getByTestID('templates-toggle--docker').should('be.visible')
|
||||
cy.getByTestID('template-install-title').should('contain', '22')
|
||||
// cy.getByTestID('templates-toggle--docker').should('be.disabled')
|
||||
|
||||
//no uncheck of variables
|
||||
cy.getByTestID('template-install-title').should('contain', '22')
|
||||
cy.getByTestID('heading-Variables').click()
|
||||
cy.getByTestID('templates-toggle--bucket').should('be.visible')
|
||||
cy.getByTestID('template-install-title').should('contain', '22')
|
||||
// cy.getByTestID('templates-toggle--bucket').should('be.disabled')
|
||||
|
||||
//can check and uncheck other resources
|
||||
cy.getByTestID('template-install-title').should('contain', '22')
|
||||
cy.getByTestID('heading-Checks').click()
|
||||
cy.getByTestID('templates-toggle--Container Disk Usage').should(
|
||||
'be.visible'
|
||||
)
|
||||
cy.getByTestID('templates-toggle--Container Disk Usage').click()
|
||||
cy.getByTestID('template-install-title').should('contain', '21')
|
||||
|
||||
cy.getByTestID('heading-Notification Rules').click()
|
||||
cy.getByTestID('templates-toggle--Crit Notifier').should('be.visible')
|
||||
cy.getByTestID('templates-toggle--Crit Notifier').click()
|
||||
cy.getByTestID('template-install-title').should('contain', '20')
|
||||
})
|
||||
|
||||
it('Can install template', () => {
|
||||
cy.getByTestID('template-install-button').click()
|
||||
cy.getByTestID('notification-success').should('be.visible')
|
||||
cy.getByTestID('installed-template-docker').should('be.visible')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Install Completed', () => {
|
||||
beforeEach(() => {
|
||||
cy.getByTestID('lookup-template-input').type(
|
||||
'https://github.com/influxdata/community-templates/blob/master/docker/docker.yml'
|
||||
)
|
||||
cy.getByTestID('lookup-template-button').click()
|
||||
cy.getByTestID('template-install-overlay').should('be.visible')
|
||||
cy.getByTestID('template-install-button').should('exist')
|
||||
cy.getByTestID('template-install-button').click()
|
||||
cy.getByTestID('notification-success').should('be.visible')
|
||||
cy.getByTestID('installed-template-docker').should('be.visible')
|
||||
})
|
||||
|
||||
it('Install Identical template', () => {
|
||||
cy.getByTestID('lookup-template-input').clear()
|
||||
cy.getByTestID('lookup-template-input').type(
|
||||
'https://github.com/influxdata/community-templates/blob/master/docker/docker.yml'
|
||||
)
|
||||
cy.getByTestID('lookup-template-button').click()
|
||||
cy.getByTestID('template-install-overlay').should('be.visible')
|
||||
cy.getByTestID('template-install-button').should('exist')
|
||||
cy.getByTestID('template-install-button').click()
|
||||
cy.getByTestID('notification-success').should('be.visible')
|
||||
cy.getByTestID('installed-template-list').should('have', '2')
|
||||
})
|
||||
|
||||
it('Can click on template resources', () => {
|
||||
//buckets
|
||||
cy.getByTestID('template-resource-link')
|
||||
.contains('Bucket')
|
||||
.click()
|
||||
cy.url().should('include', 'load-data/buckets')
|
||||
cy.go('back')
|
||||
|
||||
//telegraf
|
||||
cy.getByTestID('template-resource-link')
|
||||
.contains('Telegraf')
|
||||
.click()
|
||||
cy.url().should('include', 'load-data/telegrafs')
|
||||
cy.go('back')
|
||||
|
||||
//check
|
||||
cy.getByTestID('template-resource-link')
|
||||
.contains('Check')
|
||||
.click()
|
||||
cy.url().should('include', 'alerting/checks')
|
||||
cy.go('back')
|
||||
|
||||
//label
|
||||
cy.getByTestID('template-resource-link')
|
||||
.contains('Label')
|
||||
.click()
|
||||
cy.url().should('include', 'settings/labels')
|
||||
cy.go('back')
|
||||
|
||||
//Dashboard
|
||||
cy.getByTestID('template-resource-link')
|
||||
.contains('Dashboard')
|
||||
.click()
|
||||
cy.url().should('include', 'dashboards')
|
||||
cy.go('back')
|
||||
|
||||
//Notification Endpoint
|
||||
cy.getByTestID('template-resource-link')
|
||||
.contains('NotificationEndpoint')
|
||||
.click()
|
||||
cy.url().should('include', 'alerting')
|
||||
cy.go('back')
|
||||
|
||||
//Notification Rule
|
||||
cy.getByTestID('template-resource-link')
|
||||
.contains('NotificationRule')
|
||||
.click()
|
||||
cy.url().should('include', 'alerting')
|
||||
cy.go('back')
|
||||
|
||||
//Variable
|
||||
cy.getByTestID('template-resource-link')
|
||||
.contains('Variable')
|
||||
.click()
|
||||
cy.url().should('include', 'settings/variables')
|
||||
cy.go('back')
|
||||
})
|
||||
|
||||
it('Click on source takes you to github', () => {
|
||||
cy.getByTestID('template-source-link').should(
|
||||
'contain',
|
||||
'https://github.com/influxdata/community-templates/blob/master/docker/docker.yml'
|
||||
)
|
||||
//TODO: add the link from CLI
|
||||
})
|
||||
|
||||
it('Can delete template', () => {
|
||||
cy.getByTestID('template-delete-button-docker--button').click()
|
||||
cy.getByTestID('template-delete-button-docker--confirm-button').click()
|
||||
cy.getByTestID('installed-template-docker').should('not.be.visible')
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,32 +0,0 @@
|
|||
describe('Templates', () => {
|
||||
beforeEach(() => {
|
||||
cy.flush()
|
||||
|
||||
cy.signin().then(({body}) => {
|
||||
cy.wrap(body.org).as('org')
|
||||
cy.visit(`orgs/${body.org.id}/settings/templates`)
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps user input in text area when attempting to import invalid JSON', () => {
|
||||
cy.get('button[title*="Import"]').click()
|
||||
|
||||
cy.contains('Paste').click()
|
||||
cy.getByTestID('import-overlay--textarea')
|
||||
.click()
|
||||
.type('this is invalid JSON')
|
||||
cy.get('button[title*="Import JSON"').click()
|
||||
cy.getByTestID('import-overlay--textarea--error').should('have.length', 1)
|
||||
cy.getByTestID('import-overlay--textarea').should($s =>
|
||||
expect($s).to.contain('this is invalid JSON')
|
||||
)
|
||||
cy.getByTestID('import-overlay--textarea').type(
|
||||
'{backspace}{backspace}{backspace}{backspace}{backspace}'
|
||||
)
|
||||
cy.get('button[title*="Import JSON"').click()
|
||||
cy.getByTestID('import-overlay--textarea--error').should('have.length', 1)
|
||||
cy.getByTestID('import-overlay--textarea').should($s =>
|
||||
expect($s).to.contain('this is invalid')
|
||||
)
|
||||
})
|
||||
})
|
|
@ -135,7 +135,7 @@
|
|||
"@influxdata/clockface": "2.3.1",
|
||||
"@influxdata/flux": "^0.5.1",
|
||||
"@influxdata/flux-lsp-browser": "^0.5.11",
|
||||
"@influxdata/giraffe": "0.23.0",
|
||||
"@influxdata/giraffe": "0.24.0",
|
||||
"@influxdata/influx": "0.5.5",
|
||||
"@influxdata/influxdb-templates": "0.9.0",
|
||||
"@influxdata/react-custom-scrollbars": "4.3.8",
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
import React, {Component} from 'react'
|
||||
import {connect, ConnectedProps} from 'react-redux'
|
||||
import {Switch, Route} from 'react-router-dom'
|
||||
import uuid from 'uuid'
|
||||
|
||||
// Components
|
||||
import {Page} from '@influxdata/clockface'
|
||||
|
@ -24,8 +23,7 @@ import {event} from 'src/cloud/utils/reporting'
|
|||
import {resetQueryCache} from 'src/shared/apis/queryCache'
|
||||
import {isFlagEnabled} from 'src/shared/utils/featureFlag'
|
||||
|
||||
// Selectors & Actions
|
||||
import {setRenderID as setRenderIDAction} from 'src/perf/actions'
|
||||
// Selectors
|
||||
import {getByID} from 'src/resources/selectors'
|
||||
|
||||
// Types
|
||||
|
@ -51,34 +49,11 @@ const dashRoute = `/${ORGS}/${ORG_ID}/${DASHBOARDS}/${DASHBOARD_ID}`
|
|||
@ErrorHandling
|
||||
class DashboardPage extends Component<Props> {
|
||||
public componentDidMount() {
|
||||
const {dashboard, setRenderID} = this.props
|
||||
const renderID = uuid.v4()
|
||||
setRenderID('dashboard', renderID)
|
||||
|
||||
const tags = {
|
||||
dashboardID: dashboard.id,
|
||||
}
|
||||
const fields = {renderID}
|
||||
|
||||
event('Dashboard Mounted', tags, fields)
|
||||
if (isFlagEnabled('queryCacheForDashboards')) {
|
||||
resetQueryCache()
|
||||
}
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps) {
|
||||
const {setRenderID, dashboard, manualRefresh} = this.props
|
||||
|
||||
if (prevProps.manualRefresh !== manualRefresh) {
|
||||
const renderID = uuid.v4()
|
||||
setRenderID('dashboard', renderID)
|
||||
const tags = {
|
||||
dashboardID: dashboard.id,
|
||||
}
|
||||
const fields = {renderID}
|
||||
|
||||
event('Dashboard Mounted', tags, fields)
|
||||
}
|
||||
this.emitRenderCycleEvent()
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
|
@ -124,6 +99,20 @@ class DashboardPage extends Component<Props> {
|
|||
|
||||
return pageTitleSuffixer([title])
|
||||
}
|
||||
|
||||
private emitRenderCycleEvent = () => {
|
||||
const {dashboard, startVisitMs} = this.props
|
||||
|
||||
const tags = {
|
||||
dashboardID: dashboard.id,
|
||||
}
|
||||
|
||||
const now = new Date().getTime()
|
||||
const timeToAppearMs = now - startVisitMs
|
||||
|
||||
const fields = {timeToAppearMs}
|
||||
event('Dashboard and Variable Initial Render', tags, fields)
|
||||
}
|
||||
}
|
||||
|
||||
const mstp = (state: AppState) => {
|
||||
|
@ -134,14 +123,11 @@ const mstp = (state: AppState) => {
|
|||
)
|
||||
|
||||
return {
|
||||
startVisitMs: state.perf.dashboard.byID[dashboard.id]?.startVisitMs,
|
||||
dashboard,
|
||||
}
|
||||
}
|
||||
|
||||
const mdtp = {
|
||||
setRenderID: setRenderIDAction,
|
||||
}
|
||||
|
||||
const connector = connect(mstp, mdtp)
|
||||
const connector = connect(mstp)
|
||||
|
||||
export default connector(ManualRefresh<OwnProps>(DashboardPage))
|
||||
|
|
|
@ -8,6 +8,7 @@ import FilterList from 'src/shared/components/FilterList'
|
|||
|
||||
// Types
|
||||
import {NotificationEndpoint} from 'src/types'
|
||||
import {isFlagEnabled} from 'src/shared/utils/featureFlag'
|
||||
|
||||
interface Props {
|
||||
endpoints: NotificationEndpoint[]
|
||||
|
@ -51,13 +52,17 @@ const EmptyEndpointList: FC<{searchTerm: string}> = ({searchTerm}) => {
|
|||
</EmptyState>
|
||||
)
|
||||
}
|
||||
const conditionalEndpoints: Array<string> = []
|
||||
if (isFlagEnabled('notification-endpoint-telegram')) {
|
||||
conditionalEndpoints.push('Telegram')
|
||||
}
|
||||
|
||||
return (
|
||||
<EmptyState size={ComponentSize.Small} className="alert-column--empty">
|
||||
<EmptyState.Text>
|
||||
Want to send notifications to Slack,
|
||||
Want to send notifications to Slack, PagerDuty,
|
||||
<br />
|
||||
PagerDuty or an HTTP server?
|
||||
{conditionalEndpoints.join(', ')}or an HTTP server?
|
||||
<br />
|
||||
<br />
|
||||
Try creating a <b>Notification Endpoint</b>
|
||||
|
|
|
@ -5,6 +5,7 @@ import React, {FC, ChangeEvent} from 'react'
|
|||
import EndpointOptionsSlack from './EndpointOptionsSlack'
|
||||
import EndpointOptionsPagerDuty from './EndpointOptionsPagerDuty'
|
||||
import EndpointOptionsHTTP from './EndpointOptionsHTTP'
|
||||
import EndpointOptionsTelegram from './EndpointOptionsTelegram'
|
||||
|
||||
// Types
|
||||
import {
|
||||
|
@ -12,6 +13,7 @@ import {
|
|||
SlackNotificationEndpoint,
|
||||
PagerDutyNotificationEndpoint,
|
||||
HTTPNotificationEndpoint,
|
||||
TelegramNotificationEndpoint,
|
||||
} from 'src/types'
|
||||
|
||||
interface Props {
|
||||
|
@ -40,6 +42,16 @@ const EndpointOptions: FC<Props> = ({
|
|||
/>
|
||||
)
|
||||
}
|
||||
case 'telegram': {
|
||||
const {token, channel} = endpoint as TelegramNotificationEndpoint
|
||||
return (
|
||||
<EndpointOptionsTelegram
|
||||
token={token}
|
||||
channel={channel}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
case 'http': {
|
||||
const {
|
||||
url,
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
// Libraries
|
||||
import React, {FC, ChangeEvent} from 'react'
|
||||
|
||||
// Components
|
||||
import {
|
||||
Input,
|
||||
InputType,
|
||||
FormElement,
|
||||
Panel,
|
||||
Grid,
|
||||
Columns,
|
||||
} from '@influxdata/clockface'
|
||||
|
||||
interface Props {
|
||||
token: string
|
||||
channel: string
|
||||
onChange: (e: ChangeEvent<HTMLInputElement>) => void
|
||||
}
|
||||
|
||||
const EndpointOptionsTelegram: FC<Props> = ({token, channel, onChange}) => {
|
||||
return (
|
||||
<Panel>
|
||||
<Panel.Header>
|
||||
<h4>Telegram Options</h4>
|
||||
</Panel.Header>
|
||||
<Panel.Body>
|
||||
<Grid>
|
||||
<Grid.Row>
|
||||
<Grid.Column widthXS={Columns.Twelve}>
|
||||
<FormElement label="Bot Token">
|
||||
<Input
|
||||
name="token"
|
||||
value={token}
|
||||
testID="token"
|
||||
onChange={onChange}
|
||||
type={InputType.Password}
|
||||
/>
|
||||
</FormElement>
|
||||
<FormElement label="Chat ID">
|
||||
<Input
|
||||
name="channel"
|
||||
value={channel}
|
||||
testID="channel"
|
||||
onChange={onChange}
|
||||
type={InputType.Text}
|
||||
/>
|
||||
</FormElement>
|
||||
</Grid.Column>
|
||||
</Grid.Row>
|
||||
</Grid>
|
||||
</Panel.Body>
|
||||
</Panel>
|
||||
)
|
||||
}
|
||||
|
||||
export default EndpointOptionsTelegram
|
|
@ -59,6 +59,13 @@ export const reducer = (
|
|||
url: DEFAULT_ENDPOINT_URLS.slack,
|
||||
token: '',
|
||||
}
|
||||
case 'telegram':
|
||||
return {
|
||||
...baseProps,
|
||||
type: 'telegram',
|
||||
token: '',
|
||||
channel: '',
|
||||
}
|
||||
}
|
||||
}
|
||||
return state
|
||||
|
|
|
@ -10,6 +10,7 @@ import {extractBlockedEndpoints} from 'src/cloud/utils/limits'
|
|||
|
||||
// Types
|
||||
import {NotificationEndpointType, AppState} from 'src/types'
|
||||
import {isFlagEnabled} from 'src/shared/utils/featureFlag'
|
||||
|
||||
interface EndpointType {
|
||||
id: NotificationEndpointType
|
||||
|
@ -17,6 +18,13 @@ interface EndpointType {
|
|||
name: string
|
||||
}
|
||||
|
||||
function isFlaggedOn(type: string) {
|
||||
if (type === 'telegram') {
|
||||
return isFlagEnabled('notification-endpoint-telegram')
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
interface StateProps {
|
||||
blockedEndpoints: string[]
|
||||
}
|
||||
|
@ -32,6 +40,7 @@ const types: EndpointType[] = [
|
|||
{name: 'HTTP', type: 'http', id: 'http'},
|
||||
{name: 'Slack', type: 'slack', id: 'slack'},
|
||||
{name: 'Pagerduty', type: 'pagerduty', id: 'pagerduty'},
|
||||
{name: 'Telegram', type: 'telegram', id: 'telegram'},
|
||||
]
|
||||
|
||||
const EndpointTypeDropdown: FC<Props> = ({
|
||||
|
@ -40,7 +49,7 @@ const EndpointTypeDropdown: FC<Props> = ({
|
|||
blockedEndpoints,
|
||||
}) => {
|
||||
const items = types
|
||||
.filter(({type}) => !blockedEndpoints.includes(type))
|
||||
.filter(({type}) => !blockedEndpoints.includes(type) && isFlaggedOn(type))
|
||||
.map(({id, type, name}) => (
|
||||
<Dropdown.Item
|
||||
key={id}
|
||||
|
|
|
@ -11,6 +11,7 @@ import {AppState, NotificationEndpoint, ResourceType} from 'src/types'
|
|||
|
||||
// Utils
|
||||
import {getAll} from 'src/resources/selectors'
|
||||
import {isFlagEnabled} from 'src/shared/utils/featureFlag'
|
||||
|
||||
interface StateProps {
|
||||
endpoints: NotificationEndpoint[]
|
||||
|
@ -27,13 +28,18 @@ const EndpointsColumn: FC<Props> = ({history, match, endpoints, tabIndex}) => {
|
|||
history.push(newRuleRoute)
|
||||
}
|
||||
|
||||
const conditionalEndpoints: Array<string> = []
|
||||
if (isFlagEnabled('notification-endpoint-telegram')) {
|
||||
conditionalEndpoints.push('Telegram')
|
||||
}
|
||||
|
||||
const tooltipContents = (
|
||||
<>
|
||||
A <strong>Notification Endpoint</strong> stores the information to connect
|
||||
<br />
|
||||
to a third party service that can receive notifications
|
||||
<br />
|
||||
like Slack, PagerDuty, or an HTTP server
|
||||
like Slack, PagerDuty, {conditionalEndpoints.join(', ')}or an HTTP server
|
||||
<br />
|
||||
<br />
|
||||
<a
|
||||
|
|
|
@ -5,6 +5,7 @@ import React, {FC} from 'react'
|
|||
import SlackMessage from './SlackMessage'
|
||||
import SMTPMessage from './SMTPMessage'
|
||||
import PagerDutyMessage from './PagerDutyMessage'
|
||||
import TelegramMessage from './TelegramMessage'
|
||||
|
||||
// Utils
|
||||
import {useRuleDispatch} from './RuleOverlayProvider'
|
||||
|
@ -61,6 +62,15 @@ const RuleMessageContents: FC<Props> = ({rule}) => {
|
|||
/>
|
||||
)
|
||||
}
|
||||
case 'telegram': {
|
||||
const {messageTemplate} = rule
|
||||
return (
|
||||
<TelegramMessage
|
||||
messageTemplate={messageTemplate}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
case 'http': {
|
||||
return <></>
|
||||
}
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
// Libraries
|
||||
import React, {FC, ChangeEvent} from 'react'
|
||||
|
||||
// Components
|
||||
import {Form, TextArea} from '@influxdata/clockface'
|
||||
import {TelegramNotificationRuleBase} from 'src/types/alerting'
|
||||
|
||||
interface EventHandlers {
|
||||
onChange: (e: ChangeEvent) => void
|
||||
}
|
||||
type Props = Omit<TelegramNotificationRuleBase, 'type'> & EventHandlers
|
||||
|
||||
const TelegramMessage: FC<Props> = ({messageTemplate, onChange}) => {
|
||||
return (
|
||||
<Form.Element label="Message Template">
|
||||
<TextArea
|
||||
name="messageTemplate"
|
||||
testID="slack-message-template--textarea"
|
||||
value={messageTemplate}
|
||||
onChange={onChange}
|
||||
rows={3}
|
||||
/>
|
||||
</Form.Element>
|
||||
/*
|
||||
// keep it simple, the following elements are possible, but too advanced
|
||||
<Form.Element label="Parse Mode">
|
||||
<Input value={parseMode} name="parseMode" onChange={onChange} />
|
||||
</Form.Element>
|
||||
<Form.Element label="">
|
||||
<Input
|
||||
value={String(!disableWebPagePreview)}
|
||||
name="disableWebPagePreview"
|
||||
onChange={onChange}
|
||||
type={InputType.Checkbox}
|
||||
/>
|
||||
</Form.Element>
|
||||
*/
|
||||
)
|
||||
}
|
||||
|
||||
export default TelegramMessage
|
|
@ -8,6 +8,7 @@ import {
|
|||
SlackNotificationRuleBase,
|
||||
SMTPNotificationRuleBase,
|
||||
PagerDutyNotificationRuleBase,
|
||||
TelegramNotificationRuleBase,
|
||||
NotificationEndpoint,
|
||||
NotificationRuleDraft,
|
||||
HTTPNotificationRuleBase,
|
||||
|
@ -22,6 +23,7 @@ type RuleVariantFields =
|
|||
| SMTPNotificationRuleBase
|
||||
| PagerDutyNotificationRuleBase
|
||||
| HTTPNotificationRuleBase
|
||||
| TelegramNotificationRuleBase
|
||||
|
||||
const defaultMessage =
|
||||
'Notification Rule: ${ r._notification_rule_name } triggered by check: ${ r._check_name }: ${ r._message }'
|
||||
|
@ -45,6 +47,20 @@ export const getRuleVariantDefaults = (
|
|||
return {type: 'http', url: ''}
|
||||
}
|
||||
|
||||
case 'telegram': {
|
||||
// wrap all variable values into `` to prevent telegram's markdown errors
|
||||
const messageTemplate = defaultMessage.replace(
|
||||
/\$\{[^\}]+\}/g,
|
||||
x => `\`${x}\``
|
||||
)
|
||||
return {
|
||||
messageTemplate: messageTemplate,
|
||||
parseMode: 'MarkdownV2',
|
||||
disableWebPagePreview: false,
|
||||
type: 'telegram',
|
||||
}
|
||||
}
|
||||
|
||||
default: {
|
||||
throw new Error(`Could not find NotificationEndpoint with id "${id}"`)
|
||||
}
|
||||
|
|
|
@ -1,22 +1,15 @@
|
|||
export const SET_RENDER_ID = 'SET_RENDER_ID'
|
||||
export const SET_SCROLL = 'SET_SCROLL'
|
||||
export const SET_CELL_MOUNT = 'SET_CELL_MOUNT'
|
||||
export const SET_DASHBOARD_VISIT = 'SET_DASHBOARD_VISIT'
|
||||
|
||||
export type Action =
|
||||
| ReturnType<typeof setRenderID>
|
||||
| ReturnType<typeof setScroll>
|
||||
| ReturnType<typeof setCellMount>
|
||||
| ReturnType<typeof setDashboardVisit>
|
||||
|
||||
export type ComponentKey = 'dashboard'
|
||||
export type ScrollState = 'not scrolled' | 'scrolled'
|
||||
|
||||
export const setRenderID = (component: ComponentKey, renderID: string) =>
|
||||
({
|
||||
type: SET_RENDER_ID,
|
||||
component,
|
||||
renderID,
|
||||
} as const)
|
||||
|
||||
export const setScroll = (component: ComponentKey, scroll: ScrollState) =>
|
||||
({
|
||||
type: SET_SCROLL,
|
||||
|
@ -30,3 +23,10 @@ export const setCellMount = (cellID: string, mountStartMs: number) =>
|
|||
cellID,
|
||||
mountStartMs,
|
||||
} as const)
|
||||
|
||||
export const setDashboardVisit = (dashboardID: string, startVisitMs: number) =>
|
||||
({
|
||||
type: SET_DASHBOARD_VISIT,
|
||||
dashboardID,
|
||||
startVisitMs,
|
||||
} as const)
|
||||
|
|
|
@ -16,11 +16,10 @@ interface Props {
|
|||
const getState = (cellID: string) => (state: AppState) => {
|
||||
const {perf} = state
|
||||
const {dashboard, cells} = perf
|
||||
const {scroll, renderID} = dashboard
|
||||
const {scroll} = dashboard
|
||||
|
||||
return {
|
||||
scroll,
|
||||
renderID,
|
||||
cellMountStartMs: cells.byID[cellID]?.mountStartMs,
|
||||
}
|
||||
}
|
||||
|
@ -29,31 +28,14 @@ const CellEvent: FC<Props> = ({id, type}) => {
|
|||
const params = useParams<{dashboardID?: string}>()
|
||||
const dashboardID = params?.dashboardID
|
||||
|
||||
const {renderID, scroll, cellMountStartMs} = useSelector(getState(id))
|
||||
|
||||
useEffect(() => {
|
||||
if (scroll === 'scrolled') {
|
||||
return
|
||||
}
|
||||
|
||||
const hasIDs = dashboardID && id && renderID
|
||||
|
||||
if (!hasIDs) {
|
||||
return
|
||||
}
|
||||
|
||||
const tags = {dashboardID, cellID: id, type}
|
||||
const fields = {renderID}
|
||||
|
||||
event('Cell Visualized', tags, fields)
|
||||
}, [dashboardID, id, renderID, type, scroll])
|
||||
const {cellMountStartMs} = useSelector(getState(id))
|
||||
|
||||
useEffect(() => {
|
||||
if (!cellMountStartMs) {
|
||||
return
|
||||
}
|
||||
|
||||
const hasIDs = dashboardID && id && renderID
|
||||
const hasIDs = dashboardID && id
|
||||
if (!hasIDs) {
|
||||
return
|
||||
}
|
||||
|
@ -62,10 +44,10 @@ const CellEvent: FC<Props> = ({id, type}) => {
|
|||
const timeToAppearMs = visRenderedMs - cellMountStartMs
|
||||
|
||||
const tags = {dashboardID, cellID: id, type}
|
||||
const fields = {timeToAppearMs, renderID}
|
||||
const fields = {timeToAppearMs}
|
||||
|
||||
event('Cell Render Cycle', tags, fields)
|
||||
}, [cellMountStartMs, dashboardID, id, renderID, type])
|
||||
}, [cellMountStartMs, dashboardID, id, type])
|
||||
|
||||
return null
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import {produce} from 'immer'
|
|||
|
||||
// Actions
|
||||
import {
|
||||
SET_RENDER_ID,
|
||||
SET_DASHBOARD_VISIT,
|
||||
SET_SCROLL,
|
||||
Action,
|
||||
SET_CELL_MOUNT,
|
||||
|
@ -12,7 +12,11 @@ import {
|
|||
export interface PerfState {
|
||||
dashboard: {
|
||||
scroll: 'not scrolled' | 'scrolled'
|
||||
renderID: string
|
||||
byID: {
|
||||
[id: string]: {
|
||||
startVisitMs: number
|
||||
}
|
||||
}
|
||||
}
|
||||
cells: {
|
||||
byID: {
|
||||
|
@ -26,7 +30,7 @@ export interface PerfState {
|
|||
const initialState = (): PerfState => ({
|
||||
dashboard: {
|
||||
scroll: 'not scrolled',
|
||||
renderID: '',
|
||||
byID: {},
|
||||
},
|
||||
cells: {
|
||||
byID: {},
|
||||
|
@ -39,16 +43,24 @@ const perfReducer = (
|
|||
): PerfState =>
|
||||
produce(state, draftState => {
|
||||
switch (action.type) {
|
||||
case SET_RENDER_ID: {
|
||||
const {component, renderID} = action
|
||||
draftState[component].renderID = renderID
|
||||
case SET_SCROLL: {
|
||||
const {component, scroll} = action
|
||||
draftState[component].scroll = scroll
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
case SET_SCROLL: {
|
||||
const {component, scroll} = action
|
||||
draftState[component].scroll = scroll
|
||||
case SET_DASHBOARD_VISIT: {
|
||||
const {dashboardID, startVisitMs} = action
|
||||
const exists = draftState.dashboard.byID[dashboardID]
|
||||
|
||||
if (!exists) {
|
||||
draftState.dashboard.byID[dashboardID] = {startVisitMs}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
draftState.dashboard.byID[dashboardID].startVisitMs = startVisitMs
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -1,10 +1,21 @@
|
|||
// Libraries
|
||||
import React, {PureComponent} from 'react'
|
||||
import qs from 'qs'
|
||||
import {connect, ConnectedProps} from 'react-redux'
|
||||
import {withRouter, RouteComponentProps} from 'react-router-dom'
|
||||
|
||||
// Actions
|
||||
import {setDashboard} from 'src/shared/actions/currentDashboard'
|
||||
import {getVariables} from 'src/variables/selectors'
|
||||
import {selectValue} from 'src/variables/actions/thunks'
|
||||
import {setDashboardVisit} from 'src/perf/actions'
|
||||
|
||||
// Utils
|
||||
import {event} from 'src/cloud/utils/reporting'
|
||||
|
||||
// Selector
|
||||
import {getVariables} from 'src/variables/selectors'
|
||||
|
||||
// Types
|
||||
import {AppState} from 'src/types'
|
||||
|
||||
type ReduxProps = ConnectedProps<typeof connector>
|
||||
|
@ -44,8 +55,10 @@ class DashboardRoute extends PureComponent<Props> {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
const {dashboard, updateDashboard, variables} = this.props
|
||||
const {dashboard, updateDashboard, variables, dashboardVisit} = this.props
|
||||
const dashboardID = this.props.match.params.dashboardID
|
||||
dashboardVisit(dashboardID, new Date().getTime())
|
||||
event('Dashboard Visit', {dashboardID})
|
||||
const urlVars = qs.parse(this.props.location.search, {
|
||||
ignoreQueryPrefix: true,
|
||||
})
|
||||
|
@ -113,6 +126,7 @@ const mstp = (state: AppState) => {
|
|||
}
|
||||
|
||||
const mdtp = {
|
||||
dashboardVisit: setDashboardVisit,
|
||||
updateDashboard: setDashboard,
|
||||
selectValue: selectValue,
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ export const OSS_FLAGS = {
|
|||
'notebook-panel--spotify': false,
|
||||
'notebook-panel--test-flux': false,
|
||||
disableDefaultTableSort: false,
|
||||
'notification-endpoint-telegram': false,
|
||||
}
|
||||
|
||||
export const CLOUD_FLAGS = {
|
||||
|
@ -33,6 +34,7 @@ export const CLOUD_FLAGS = {
|
|||
'notebook-panel--spotify': false,
|
||||
'notebook-panel--test-flux': false,
|
||||
disableDefaultTableSort: false,
|
||||
'notification-endpoint-telegram': false,
|
||||
}
|
||||
|
||||
export const activeFlags = (state: AppState): FlagMap => {
|
||||
|
|
|
@ -31,7 +31,7 @@ export type Action =
|
|||
| ReturnType<typeof setExportTemplate>
|
||||
| ReturnType<typeof setTemplatesStatus>
|
||||
| ReturnType<typeof setTemplateSummary>
|
||||
| ReturnType<typeof setCommunityTemplateToInstall>
|
||||
| ReturnType<typeof setStagedCommunityTemplate>
|
||||
| ReturnType<typeof toggleTemplateResourceInstall>
|
||||
| ReturnType<typeof setStacks>
|
||||
| ReturnType<typeof removeStack>
|
||||
|
@ -91,7 +91,7 @@ export const setTemplateSummary = (
|
|||
schema,
|
||||
} as const)
|
||||
|
||||
export const setCommunityTemplateToInstall = (template: CommunityTemplate) =>
|
||||
export const setStagedCommunityTemplate = (template: CommunityTemplate) =>
|
||||
({
|
||||
type: SET_COMMUNITY_TEMPLATE_TO_INSTALL,
|
||||
template,
|
||||
|
|
|
@ -3,10 +3,10 @@ import {withRouter, RouteComponentProps} from 'react-router-dom'
|
|||
import {connect, ConnectedProps} from 'react-redux'
|
||||
|
||||
// Components
|
||||
import {CommunityTemplateInstallerOverlay} from 'src/templates/components/CommunityTemplateInstallerOverlay'
|
||||
import {CommunityTemplateOverlay} from 'src/templates/components/CommunityTemplateOverlay'
|
||||
|
||||
// Actions
|
||||
import {setCommunityTemplateToInstall} from 'src/templates/actions/creators'
|
||||
import {setStagedCommunityTemplate} from 'src/templates/actions/creators'
|
||||
import {createTemplate, fetchAndSetStacks} from 'src/templates/actions/thunks'
|
||||
import {notify} from 'src/shared/actions/notifications'
|
||||
|
||||
|
@ -62,12 +62,8 @@ class UnconnectedTemplateImportOverlay extends PureComponent<Props> {
|
|||
}
|
||||
|
||||
public render() {
|
||||
if (!this.props.flags.communityTemplates) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<CommunityTemplateInstallerOverlay
|
||||
<CommunityTemplateOverlay
|
||||
onDismissOverlay={this.onDismiss}
|
||||
onInstall={this.handleInstallTemplate}
|
||||
resourceCount={this.props.resourceCount}
|
||||
|
@ -93,7 +89,7 @@ class UnconnectedTemplateImportOverlay extends PureComponent<Props> {
|
|||
try {
|
||||
const summary = await reviewTemplate(orgID, yamlLocation)
|
||||
|
||||
this.props.setCommunityTemplateToInstall(summary)
|
||||
this.props.setStagedCommunityTemplate(summary)
|
||||
return summary
|
||||
} catch (err) {
|
||||
this.props.notify(communityTemplateInstallFailed(err.message))
|
||||
|
@ -161,17 +157,17 @@ const mstp = (state: AppState, props: RouterProps) => {
|
|||
templateExtension: props.match.params.templateExtension,
|
||||
flags: state.flags.original,
|
||||
resourceCount: getTotalResourceCount(
|
||||
state.resources.templates.communityTemplateToInstall.summary
|
||||
state.resources.templates.stagedCommunityTemplate.summary
|
||||
),
|
||||
resourcesToSkip:
|
||||
state.resources.templates.communityTemplateToInstall.resourcesToSkip,
|
||||
state.resources.templates.stagedCommunityTemplate.resourcesToSkip,
|
||||
}
|
||||
}
|
||||
|
||||
const mdtp = {
|
||||
createTemplate,
|
||||
notify,
|
||||
setCommunityTemplateToInstall,
|
||||
setStagedCommunityTemplate,
|
||||
fetchAndSetStacks,
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,8 @@ import {
|
|||
AlignItems,
|
||||
InfluxColors,
|
||||
} from '@influxdata/clockface'
|
||||
import CommunityTemplateNameIcon from 'src/templates/components/CommunityTemplateNameIcon'
|
||||
|
||||
import {CommunityTemplateInstallInstructionsIcon} from 'src/templates/components/CommunityTemplateInstallInstructionsIcon'
|
||||
|
||||
interface Props {
|
||||
templateName: string
|
||||
|
@ -23,9 +24,7 @@ interface Props {
|
|||
onClickInstall?: () => void
|
||||
}
|
||||
|
||||
import {} from 'react'
|
||||
|
||||
const CommunityTemplateName: FC<Props> = ({
|
||||
export const CommunityTemplateInstallInstructions: FC<Props> = ({
|
||||
templateName,
|
||||
resourceCount,
|
||||
onClickInstall,
|
||||
|
@ -44,6 +43,7 @@ const CommunityTemplateName: FC<Props> = ({
|
|||
color={ComponentColor.Success}
|
||||
size={ComponentSize.Medium}
|
||||
onClick={onClickInstall}
|
||||
testID="template-install-button"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -55,13 +55,13 @@ const CommunityTemplateName: FC<Props> = ({
|
|||
direction={FlexDirection.Row}
|
||||
alignItems={AlignItems.Center}
|
||||
>
|
||||
<CommunityTemplateNameIcon
|
||||
<CommunityTemplateInstallInstructionsIcon
|
||||
strokeWidth={2}
|
||||
strokeColor={InfluxColors.Neutrino}
|
||||
width={54}
|
||||
height={54}
|
||||
/>
|
||||
<FlexBox.Child grow={1} shrink={0}>
|
||||
<FlexBox.Child grow={1} shrink={0} testID="template-install-title">
|
||||
<Heading
|
||||
className="community-templates--template-name"
|
||||
element={HeadingElement.H4}
|
||||
|
@ -86,5 +86,3 @@ const CommunityTemplateName: FC<Props> = ({
|
|||
</Panel>
|
||||
)
|
||||
}
|
||||
|
||||
export default CommunityTemplateName
|
|
@ -8,7 +8,7 @@ interface Props {
|
|||
height: number
|
||||
}
|
||||
|
||||
const CommunityTemplateNameIcon: FC<Props> = ({
|
||||
export const CommunityTemplateInstallInstructionsIcon: FC<Props> = ({
|
||||
strokeColor,
|
||||
fillColor = 'none',
|
||||
strokeWidth = 2,
|
||||
|
@ -73,5 +73,3 @@ const CommunityTemplateNameIcon: FC<Props> = ({
|
|||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default CommunityTemplateNameIcon
|
|
@ -47,7 +47,9 @@ const CommunityTemplateListGroup: FC<Props> = ({title, count, children}) => {
|
|||
<div className="community-templates--list-toggle">
|
||||
<Icon glyph={IconFont.CaretRight} />
|
||||
</div>
|
||||
<Heading element={HeadingElement.H5}>{title}</Heading>
|
||||
<Heading element={HeadingElement.H5} testID={`heading-${title}`}>
|
||||
{title}
|
||||
</Heading>
|
||||
<Heading
|
||||
element={HeadingElement.Div}
|
||||
appearance={HeadingElement.H6}
|
||||
|
|
|
@ -58,6 +58,7 @@ const CommunityTemplateListItem: FC<Props> = ({
|
|||
icon={IconFont.Checkmark}
|
||||
color={ComponentColor.Success}
|
||||
disabled={shouldDisableToggle}
|
||||
testID={`templates-toggle--${title}`}
|
||||
/>
|
||||
<FlexBox
|
||||
alignItems={AlignItems.FlexStart}
|
||||
|
|
|
@ -4,9 +4,9 @@ import {withRouter, RouteComponentProps} from 'react-router-dom'
|
|||
|
||||
// Components
|
||||
import {Alignment, Orientation, Overlay, Tabs} from '@influxdata/clockface'
|
||||
import CommunityTemplateName from 'src/templates/components/CommunityTemplateName'
|
||||
import {CommunityTemplateInstallInstructions} from 'src/templates/components/CommunityTemplateInstallInstructions'
|
||||
import {CommunityTemplateReadme} from 'src/templates/components/CommunityTemplateReadme'
|
||||
import {CommunityTemplateContents} from 'src/templates/components/CommunityTemplateContents'
|
||||
import {CommunityTemplateOverlayContents} from 'src/templates/components/CommunityTemplateOverlayContents'
|
||||
|
||||
// Types
|
||||
import {ComponentStatus} from '@influxdata/clockface'
|
||||
|
@ -34,10 +34,7 @@ type ActiveTab = Tab.IncludedResources | Tab.Readme
|
|||
|
||||
type Props = OwnProps & RouteComponentProps<{orgID: string}>
|
||||
|
||||
class CommunityTemplateInstallerOverlayUnconnected extends PureComponent<
|
||||
Props,
|
||||
State
|
||||
> {
|
||||
class CommunityTemplateOverlayUnconnected extends PureComponent<Props, State> {
|
||||
state: State = {
|
||||
activeTab: Tab.IncludedResources,
|
||||
}
|
||||
|
@ -51,13 +48,13 @@ class CommunityTemplateInstallerOverlayUnconnected extends PureComponent<
|
|||
|
||||
return (
|
||||
<Overlay visible={isVisible}>
|
||||
<Overlay.Container maxWidth={800}>
|
||||
<Overlay.Container maxWidth={800} testID="template-install-overlay">
|
||||
<Overlay.Header
|
||||
title="Template Installer"
|
||||
onDismiss={this.onDismiss}
|
||||
/>
|
||||
<Overlay.Body>
|
||||
<CommunityTemplateName
|
||||
<CommunityTemplateInstallInstructions
|
||||
templateName={templateName}
|
||||
resourceCount={resourceCount}
|
||||
onClickInstall={onInstall}
|
||||
|
@ -78,7 +75,7 @@ class CommunityTemplateInstallerOverlayUnconnected extends PureComponent<
|
|||
/>
|
||||
</Tabs.Tabs>
|
||||
{this.state.activeTab === Tab.IncludedResources ? (
|
||||
<CommunityTemplateContents />
|
||||
<CommunityTemplateOverlayContents />
|
||||
) : (
|
||||
<CommunityTemplateReadme />
|
||||
)}
|
||||
|
@ -102,6 +99,6 @@ class CommunityTemplateInstallerOverlayUnconnected extends PureComponent<
|
|||
}
|
||||
}
|
||||
|
||||
export const CommunityTemplateInstallerOverlay = withRouter(
|
||||
CommunityTemplateInstallerOverlayUnconnected
|
||||
export const CommunityTemplateOverlay = withRouter(
|
||||
CommunityTemplateOverlayUnconnected
|
||||
)
|
|
@ -23,7 +23,7 @@ import {getResourceInstallCount} from 'src/templates/selectors'
|
|||
type ReduxProps = ConnectedProps<typeof connector>
|
||||
type Props = ReduxProps
|
||||
|
||||
class CommunityTemplateContentsUnconnected extends PureComponent<Props> {
|
||||
class CommunityTemplateOverlayContentsUnconnected extends PureComponent<Props> {
|
||||
render() {
|
||||
const {summary} = this.props
|
||||
if (!Object.keys(summary).length) {
|
||||
|
@ -219,7 +219,7 @@ class CommunityTemplateContentsUnconnected extends PureComponent<Props> {
|
|||
}
|
||||
|
||||
const mstp = (state: AppState) => {
|
||||
return {summary: state.resources.templates.communityTemplateToInstall.summary}
|
||||
return {summary: state.resources.templates.stagedCommunityTemplate.summary}
|
||||
}
|
||||
|
||||
const mdtp = {
|
||||
|
@ -228,6 +228,6 @@ const mdtp = {
|
|||
|
||||
const connector = connect(mstp, mdtp)
|
||||
|
||||
export const CommunityTemplateContents = connector(
|
||||
CommunityTemplateContentsUnconnected
|
||||
export const CommunityTemplateOverlayContents = connector(
|
||||
CommunityTemplateOverlayContentsUnconnected
|
||||
)
|
|
@ -238,20 +238,26 @@ class CommunityTemplatesInstalledListUnconnected extends PureComponent<Props> {
|
|||
<Table.Body>
|
||||
{this.props.stacks.map(stack => {
|
||||
return (
|
||||
<Table.Row key={`stack-${stack.id}`}>
|
||||
<Table.Cell>{stack.name}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Table.Row
|
||||
testID="installed-template-list"
|
||||
key={`stack-${stack.id}`}
|
||||
>
|
||||
<Table.Cell testID={`installed-template-${stack.name}`}>
|
||||
{stack.name}
|
||||
</Table.Cell>
|
||||
<Table.Cell testID="template-resource-link">
|
||||
{this.renderStackResources(stack.resources)}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
{new Date(stack.createdAt).toDateString()}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Table.Cell testID="template-source-link">
|
||||
{this.renderStackSources(stack.sources)}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<ConfirmationButton
|
||||
confirmationButtonText="Delete"
|
||||
testID={`template-delete-button-${stack.name}`}
|
||||
confirmationButtonColor={ComponentColor.Danger}
|
||||
confirmationLabel="Really Delete All Resources?"
|
||||
popoverColor={ComponentColor.Default}
|
||||
|
|
|
@ -110,6 +110,7 @@ class UnconnectedTemplatesIndex extends Component<Props> {
|
|||
size={ComponentSize.Small}
|
||||
target={LinkTarget.Blank}
|
||||
text="Browse Community Templates"
|
||||
testID="browse-template-button"
|
||||
/>
|
||||
</Panel.SymbolHeader>
|
||||
</Panel>
|
||||
|
@ -131,11 +132,13 @@ class UnconnectedTemplatesIndex extends Component<Props> {
|
|||
placeholder="Enter the URL of an InfluxDB Template..."
|
||||
style={{width: '80%'}}
|
||||
value={this.state.templateUrl}
|
||||
testID="lookup-template-input"
|
||||
/>
|
||||
<Button
|
||||
onClick={this.startTemplateInstall}
|
||||
size={ComponentSize.Small}
|
||||
text="Lookup Template"
|
||||
testID="lookup-template-button"
|
||||
/>
|
||||
</div>
|
||||
</Panel.Body>
|
||||
|
|
|
@ -12,7 +12,6 @@ import TemplatesPage from 'src/templates/components/TemplatesPage'
|
|||
import GetResources from 'src/resources/components/GetResources'
|
||||
import TemplateImportOverlay from 'src/templates/components/TemplateImportOverlay'
|
||||
import TemplateExportOverlay from 'src/templates/components/TemplateExportOverlay'
|
||||
import {CommunityTemplateImportOverlay} from 'src/templates/components/CommunityTemplateImportOverlay'
|
||||
import TemplateViewOverlay from 'src/templates/components/TemplateViewOverlay'
|
||||
import StaticTemplateViewOverlay from 'src/templates/components/StaticTemplateViewOverlay'
|
||||
|
||||
|
@ -54,10 +53,6 @@ class TemplatesIndex extends Component<Props> {
|
|||
path={`${templatesPath}/import`}
|
||||
component={TemplateImportOverlay}
|
||||
/>
|
||||
<Route
|
||||
path={`${templatesPath}/import/:templateName`}
|
||||
component={CommunityTemplateImportOverlay}
|
||||
/>
|
||||
<Route
|
||||
path={`${templatesPath}/:id/export`}
|
||||
component={TemplateExportOverlay}
|
||||
|
|
|
@ -42,10 +42,10 @@ const templateSummary = {
|
|||
|
||||
const exportTemplate = {status, item: null}
|
||||
|
||||
const communityTemplateToInstall: CommunityTemplate = {}
|
||||
const stagedCommunityTemplate: CommunityTemplate = {}
|
||||
|
||||
const initialState = () => ({
|
||||
communityTemplateToInstall,
|
||||
stagedCommunityTemplate,
|
||||
status,
|
||||
byID: {
|
||||
['1']: templateSummary,
|
||||
|
@ -98,7 +98,7 @@ describe('templates reducer', () => {
|
|||
byID,
|
||||
allIDs,
|
||||
exportTemplate,
|
||||
communityTemplateToInstall,
|
||||
stagedCommunityTemplate,
|
||||
stacks: [],
|
||||
}
|
||||
const actual = reducer(state, removeTemplateSummary(state.allIDs[1]))
|
||||
|
|
|
@ -36,7 +36,7 @@ const defaultCommunityTemplate = (): CommunityTemplate => {
|
|||
}
|
||||
|
||||
export const defaultState = (): TemplatesState => ({
|
||||
communityTemplateToInstall: defaultCommunityTemplate(),
|
||||
stagedCommunityTemplate: defaultCommunityTemplate(),
|
||||
status: RemoteDataState.NotStarted,
|
||||
byID: {},
|
||||
allIDs: [],
|
||||
|
@ -78,12 +78,12 @@ export const templatesReducer = (
|
|||
case SET_COMMUNITY_TEMPLATE_TO_INSTALL: {
|
||||
const {template} = action
|
||||
|
||||
const communityTemplateToInstall = {
|
||||
const stagedCommunityTemplate = {
|
||||
...defaultCommunityTemplate(),
|
||||
...template,
|
||||
}
|
||||
|
||||
communityTemplateToInstall.summary.dashboards = (
|
||||
stagedCommunityTemplate.summary.dashboards = (
|
||||
template.summary.dashboards || []
|
||||
).map(dashboard => {
|
||||
if (!dashboard.hasOwnProperty('shouldInstall')) {
|
||||
|
@ -92,7 +92,7 @@ export const templatesReducer = (
|
|||
return dashboard
|
||||
})
|
||||
|
||||
communityTemplateToInstall.summary.telegrafConfigs = (
|
||||
stagedCommunityTemplate.summary.telegrafConfigs = (
|
||||
template.summary.telegrafConfigs || []
|
||||
).map(telegrafConfig => {
|
||||
if (!telegrafConfig.hasOwnProperty('shouldInstall')) {
|
||||
|
@ -101,7 +101,7 @@ export const templatesReducer = (
|
|||
return telegrafConfig
|
||||
})
|
||||
|
||||
communityTemplateToInstall.summary.buckets = (
|
||||
stagedCommunityTemplate.summary.buckets = (
|
||||
template.summary.buckets || []
|
||||
).map(bucket => {
|
||||
if (!bucket.hasOwnProperty('shouldInstall')) {
|
||||
|
@ -110,7 +110,7 @@ export const templatesReducer = (
|
|||
return bucket
|
||||
})
|
||||
|
||||
communityTemplateToInstall.summary.checks = (
|
||||
stagedCommunityTemplate.summary.checks = (
|
||||
template.summary.checks || []
|
||||
).map(check => {
|
||||
if (!check.hasOwnProperty('shouldInstall')) {
|
||||
|
@ -119,7 +119,7 @@ export const templatesReducer = (
|
|||
return check
|
||||
})
|
||||
|
||||
communityTemplateToInstall.summary.variables = (
|
||||
stagedCommunityTemplate.summary.variables = (
|
||||
template.summary.variables || []
|
||||
).map(variable => {
|
||||
if (!variable.hasOwnProperty('shouldInstall')) {
|
||||
|
@ -128,7 +128,7 @@ export const templatesReducer = (
|
|||
return variable
|
||||
})
|
||||
|
||||
communityTemplateToInstall.summary.notificationRules = (
|
||||
stagedCommunityTemplate.summary.notificationRules = (
|
||||
template.summary.notificationRules || []
|
||||
).map(notificationRule => {
|
||||
if (!notificationRule.hasOwnProperty('shouldInstall')) {
|
||||
|
@ -137,7 +137,7 @@ export const templatesReducer = (
|
|||
return notificationRule
|
||||
})
|
||||
|
||||
communityTemplateToInstall.summary.notificationEndpoints = (
|
||||
stagedCommunityTemplate.summary.notificationEndpoints = (
|
||||
template.summary.notificationEndpoints || []
|
||||
).map(notificationEndpoint => {
|
||||
if (!notificationEndpoint.hasOwnProperty('shouldInstall')) {
|
||||
|
@ -146,7 +146,7 @@ export const templatesReducer = (
|
|||
return notificationEndpoint
|
||||
})
|
||||
|
||||
communityTemplateToInstall.summary.labels = (
|
||||
stagedCommunityTemplate.summary.labels = (
|
||||
template.summary.labels || []
|
||||
).map(label => {
|
||||
if (!label.hasOwnProperty('shouldInstall')) {
|
||||
|
@ -155,7 +155,7 @@ export const templatesReducer = (
|
|||
return label
|
||||
})
|
||||
|
||||
communityTemplateToInstall.summary.summaryTask = (
|
||||
stagedCommunityTemplate.summary.summaryTask = (
|
||||
template.summary.summaryTask || []
|
||||
).map(summaryTask => {
|
||||
if (!summaryTask.hasOwnProperty('shouldInstall')) {
|
||||
|
@ -164,7 +164,7 @@ export const templatesReducer = (
|
|||
return summaryTask
|
||||
})
|
||||
|
||||
draftState.communityTemplateToInstall = communityTemplateToInstall
|
||||
draftState.stagedCommunityTemplate = stagedCommunityTemplate
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -195,7 +195,7 @@ export const templatesReducer = (
|
|||
case TOGGLE_TEMPLATE_RESOURCE_INSTALL: {
|
||||
const {resourceType, shouldInstall, templateMetaName} = action
|
||||
|
||||
const templateToInstall = {...draftState.communityTemplateToInstall}
|
||||
const templateToInstall = {...draftState.stagedCommunityTemplate}
|
||||
|
||||
templateToInstall.summary[resourceType].forEach(resource => {
|
||||
if (resource.templateMetaName === templateMetaName) {
|
||||
|
@ -214,7 +214,7 @@ export const templatesReducer = (
|
|||
}
|
||||
})
|
||||
|
||||
draftState.communityTemplateToInstall = templateToInstall
|
||||
draftState.stagedCommunityTemplate = templateToInstall
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,8 @@ import {
|
|||
Threshold,
|
||||
CheckBase as GenCheckBase,
|
||||
NotificationEndpointBase as GenEndpointBase,
|
||||
TelegramNotificationRuleBase,
|
||||
TelegramNotificationEndpoint,
|
||||
} from 'src/client'
|
||||
|
||||
import {RemoteDataState} from 'src/types'
|
||||
|
@ -50,6 +52,8 @@ export type NotificationEndpoint =
|
|||
| (Omit<PagerDutyNotificationEndpoint, 'status' | 'labels'> &
|
||||
EndpointOverrides)
|
||||
| (Omit<HTTPNotificationEndpoint, 'status' | 'labels'> & EndpointOverrides)
|
||||
| (Omit<TelegramNotificationEndpoint, 'status' | 'labels'> &
|
||||
EndpointOverrides)
|
||||
export type NotificationEndpointBase = Omit<GenEndpointBase, 'labels'> &
|
||||
EndpointOverrides
|
||||
|
||||
|
@ -76,7 +80,7 @@ export type NotificationRuleBaseDraft = Overwrite<
|
|||
}
|
||||
>
|
||||
|
||||
type RuleDraft = SlackRule | SMTPRule | PagerDutyRule | HTTPRule
|
||||
type RuleDraft = SlackRule | SMTPRule | PagerDutyRule | HTTPRule | TelegramRule
|
||||
|
||||
export type NotificationRuleDraft = RuleDraft
|
||||
|
||||
|
@ -96,6 +100,10 @@ type HTTPRule = NotificationRuleBaseDraft &
|
|||
HTTPNotificationRuleBase &
|
||||
RuleOverrides
|
||||
|
||||
type TelegramRule = NotificationRuleBaseDraft &
|
||||
TelegramNotificationRuleBase &
|
||||
RuleOverrides
|
||||
|
||||
export type LowercaseCheckStatusLevel =
|
||||
| 'crit'
|
||||
| 'warn'
|
||||
|
@ -189,4 +197,7 @@ export {
|
|||
PostNotificationRule,
|
||||
CheckPatch,
|
||||
TaskStatusType,
|
||||
TelegramNotificationEndpoint,
|
||||
TelegramNotificationRuleBase,
|
||||
TelegramNotificationRule,
|
||||
} from '../client'
|
||||
|
|
|
@ -35,7 +35,7 @@ export type CommunityTemplate = any
|
|||
|
||||
export interface TemplatesState extends NormalizedState<TemplateSummary> {
|
||||
exportTemplate: {status: RemoteDataState; item: DocumentCreate}
|
||||
communityTemplateToInstall: CommunityTemplate
|
||||
stagedCommunityTemplate: CommunityTemplate
|
||||
stacks: InstalledStack[]
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue