Merge branch 'master' into feat/use-algo-w

pull/18139/head
Jonathan A. Sternberg 2020-05-18 13:25:26 -05:00
commit 0c8d19e896
No known key found for this signature in database
GPG Key ID: 4A0C1200CB8B9D2E
267 changed files with 32165 additions and 732 deletions

View File

@ -3,9 +3,14 @@
### Features
1. [18011](https://github.com/influxdata/influxdb/pull/18011): Integrate UTC dropdown when making custom time range query
1. [18040](https://github.com/influxdata/influxdb/pull/18040): Allow for min OR max y-axis visualization settings rather than min AND max
1. [17764](https://github.com/influxdata/influxdb/pull/17764): Add CSV to line protocol conversion library
### Bug Fixes
1. [18066](https://github.com/influxdata/influxdb/pull/18066): Fixed bug that wasn't persisting timeFormat for Graph + Single Stat selections
1. [17959](https://github.com/influxdata/influxdb/pull/17959): Authorizer now exposes full permission set
### UI Improvements
## v2.0.0-beta.10 [2020-05-07]

12
auth.go
View File

@ -46,14 +46,16 @@ func (a *Authorization) Valid() error {
return nil
}
// Allowed returns true if the authorization is active and request permission
// exists in the authorization's list of permissions.
func (a *Authorization) Allowed(p Permission) bool {
// PermissionSet returns the set of permissions associated with the Authorization.
func (a *Authorization) PermissionSet() (PermissionSet, error) {
if !a.IsActive() {
return false
return nil, &Error{
Code: EUnauthorized,
Msg: "token is inactive",
}
}
return PermissionAllowed(p, a.Permissions)
return a.Permissions, nil
}
// IsActive is a stub for idpe.

View File

@ -75,13 +75,13 @@ func (h *AuthHandler) handlePostAuthorization(w http.ResponseWriter, r *http.Req
ctx := r.Context()
a, err := decodePostAuthorizationRequest(ctx, r)
if err != nil {
h.api.Err(w, err)
h.api.Err(w, r, err)
return
}
user, err := getAuthorizedUser(r, h.tenantService)
if err != nil {
h.api.Err(w, influxdb.ErrUnableToCreateToken)
h.api.Err(w, r, influxdb.ErrUnableToCreateToken)
return
}
@ -93,13 +93,13 @@ func (h *AuthHandler) handlePostAuthorization(w http.ResponseWriter, r *http.Req
auth := a.toInfluxdb(userID)
if err := h.authSvc.CreateAuthorization(ctx, auth); err != nil {
h.api.Err(w, err)
h.api.Err(w, r, err)
return
}
perms, err := newPermissionsResponse(ctx, auth.Permissions, h.lookupService)
if err != nil {
h.api.Err(w, err)
h.api.Err(w, r, err)
return
}
@ -107,11 +107,11 @@ func (h *AuthHandler) handlePostAuthorization(w http.ResponseWriter, r *http.Req
resp, err := h.newAuthResponse(ctx, auth, perms)
if err != nil {
h.api.Err(w, err)
h.api.Err(w, r, err)
return
}
h.api.Respond(w, http.StatusCreated, resp)
h.api.Respond(w, r, http.StatusCreated, resp)
}
func getAuthorizedUser(r *http.Request, ts TenantService) (*influxdb.User, error) {
@ -352,7 +352,7 @@ func (h *AuthHandler) handleGetAuthorizations(w http.ResponseWriter, r *http.Req
req, err := decodeGetAuthorizationsRequest(ctx, r)
if err != nil {
h.log.Info("Failed to decode request", zap.String("handler", "getAuthorizations"), zap.Error(err))
h.api.Err(w, err)
h.api.Err(w, r, err)
return
}
@ -360,7 +360,7 @@ func (h *AuthHandler) handleGetAuthorizations(w http.ResponseWriter, r *http.Req
as, _, err := h.authSvc.FindAuthorizations(ctx, req.filter, opts)
if err != nil {
h.api.Err(w, err)
h.api.Err(w, r, err)
return
}
@ -369,7 +369,7 @@ func (h *AuthHandler) handleGetAuthorizations(w http.ResponseWriter, r *http.Req
if f.User != nil {
u, err := h.tenantService.FindUser(ctx, influxdb.UserFilter{Name: f.User})
if err != nil {
h.api.Err(w, err)
h.api.Err(w, r, err)
return
}
f.UserID = &u.ID
@ -378,7 +378,7 @@ func (h *AuthHandler) handleGetAuthorizations(w http.ResponseWriter, r *http.Req
if f.Org != nil {
o, err := h.tenantService.FindOrganization(ctx, influxdb.OrganizationFilter{Name: f.Org})
if err != nil {
h.api.Err(w, err)
h.api.Err(w, r, err)
return
}
f.OrgID = &o.ID
@ -388,7 +388,7 @@ func (h *AuthHandler) handleGetAuthorizations(w http.ResponseWriter, r *http.Req
for _, a := range as {
ps, err := newPermissionsResponse(ctx, a.Permissions, h.lookupService)
if err != nil {
h.api.Err(w, err)
h.api.Err(w, r, err)
return
}
@ -402,7 +402,7 @@ func (h *AuthHandler) handleGetAuthorizations(w http.ResponseWriter, r *http.Req
h.log.Debug("Auths retrieved ", zap.String("auths", fmt.Sprint(auths)))
h.api.Respond(w, http.StatusOK, newAuthsResponse(auths))
h.api.Respond(w, r, http.StatusOK, newAuthsResponse(auths))
}
type getAuthorizationsRequest struct {
@ -460,20 +460,20 @@ func (h *AuthHandler) handleGetAuthorization(w http.ResponseWriter, r *http.Requ
id, err := influxdb.IDFromString(chi.URLParam(r, "id"))
if err != nil {
h.log.Info("Failed to decode request", zap.String("handler", "getAuthorization"), zap.Error(err))
h.api.Err(w, err)
h.api.Err(w, r, err)
return
}
a, err := h.authSvc.FindAuthorizationByID(ctx, *id)
if err != nil {
// Don't log here, it should already be handled by the service
h.api.Err(w, err)
h.api.Err(w, r, err)
return
}
ps, err := newPermissionsResponse(ctx, a.Permissions, h.lookupService)
if err != nil {
h.api.Err(w, err)
h.api.Err(w, r, err)
return
}
@ -481,11 +481,11 @@ func (h *AuthHandler) handleGetAuthorization(w http.ResponseWriter, r *http.Requ
resp, err := h.newAuthResponse(ctx, a, ps)
if err != nil {
h.api.Err(w, err)
h.api.Err(w, r, err)
return
}
h.api.Respond(w, http.StatusOK, resp)
h.api.Respond(w, r, http.StatusOK, resp)
}
// handleUpdateAuthorization is the HTTP handler for the PATCH /api/v2/authorizations/:id route that updates the authorization's status and desc.
@ -494,36 +494,36 @@ func (h *AuthHandler) handleUpdateAuthorization(w http.ResponseWriter, r *http.R
req, err := decodeUpdateAuthorizationRequest(ctx, r)
if err != nil {
h.log.Info("Failed to decode request", zap.String("handler", "updateAuthorization"), zap.Error(err))
h.api.Err(w, err)
h.api.Err(w, r, err)
return
}
a, err := h.authSvc.FindAuthorizationByID(ctx, req.ID)
if err != nil {
h.api.Err(w, err)
h.api.Err(w, r, err)
return
}
a, err = h.authSvc.UpdateAuthorization(ctx, a.ID, req.AuthorizationUpdate)
if err != nil {
h.api.Err(w, err)
h.api.Err(w, r, err)
return
}
ps, err := newPermissionsResponse(ctx, a.Permissions, h.lookupService)
if err != nil {
h.api.Err(w, err)
h.api.Err(w, r, err)
return
}
h.log.Debug("Auth updated", zap.String("auth", fmt.Sprint(a)))
resp, err := h.newAuthResponse(ctx, a, ps)
if err != nil {
h.api.Err(w, err)
h.api.Err(w, r, err)
return
}
h.api.Respond(w, http.StatusOK, resp)
h.api.Respond(w, r, http.StatusOK, resp)
}
type updateAuthorizationRequest struct {
@ -553,13 +553,13 @@ func (h *AuthHandler) handleDeleteAuthorization(w http.ResponseWriter, r *http.R
id, err := influxdb.IDFromString(chi.URLParam(r, "id"))
if err != nil {
h.log.Info("Failed to decode request", zap.String("handler", "deleteAuthorization"), zap.Error(err))
h.api.Err(w, err)
h.api.Err(w, r, err)
return
}
if err := h.authSvc.DeleteAuthorization(r.Context(), *id); err != nil {
// Don't log here, it should already be handled by the service
h.api.Err(w, err)
h.api.Err(w, r, err)
return
}

View File

@ -9,8 +9,13 @@ import (
)
func isAllowedAll(a influxdb.Authorizer, permissions []influxdb.Permission) error {
pset, err := a.PermissionSet()
if err != nil {
return err
}
for _, p := range permissions {
if !a.Allowed(p) {
if !pset.Allowed(p) {
return &influxdb.Error{
Code: influxdb.EUnauthorized,
Msg: fmt.Sprintf("%s is unauthorized", p),
@ -47,8 +52,12 @@ func IsAllowedAny(ctx context.Context, permissions []influxdb.Permission) error
if err != nil {
return err
}
pset, err := a.PermissionSet()
if err != nil {
return err
}
for _, p := range permissions {
if a.Allowed(p) {
if pset.Allowed(p) {
return nil
}
}

View File

@ -6,6 +6,24 @@ import (
"github.com/influxdata/influxdb/v2"
)
// AuthorizeFindDBRPs takes the given items and returns only the ones that the user is authorized to read.
func AuthorizeFindDBRPs(ctx context.Context, rs []*influxdb.DBRPMappingV2) ([]*influxdb.DBRPMappingV2, int, error) {
// This filters without allocating
// https://github.com/golang/go/wiki/SliceTricks#filtering-without-allocating
rrs := rs[:0]
for _, r := range rs {
_, _, err := AuthorizeRead(ctx, influxdb.DBRPResourceType, r.ID, r.OrganizationID)
if err != nil && influxdb.ErrorCode(err) != influxdb.EUnauthorized {
return nil, 0, err
}
if influxdb.ErrorCode(err) == influxdb.EUnauthorized {
continue
}
rrs = append(rrs, r)
}
return rrs, len(rrs), nil
}
// AuthorizeFindAuthorizations takes the given items and returns only the ones that the user is authorized to read.
func AuthorizeFindAuthorizations(ctx context.Context, rs []*influxdb.Authorization) ([]*influxdb.Authorization, int, error) {
// This filters without allocating

View File

@ -17,8 +17,8 @@ var (
// Authorizer will authorize a permission.
type Authorizer interface {
// Allowed returns true is the associated permission is allowed by the authorizer
Allowed(p Permission) bool
// PermissionSet returns the PermissionSet associated with the authorizer
PermissionSet() (PermissionSet, error)
// ID returns an identifier used for auditing.
Identifier() ID
@ -129,6 +129,8 @@ const (
NotificationEndpointResourceType = ResourceType("notificationEndpoints") // 15
// ChecksResourceType gives permission to one or more Checks.
ChecksResourceType = ResourceType("checks") // 16
// DBRPType gives permission to one or more DBRPs.
DBRPResourceType = ResourceType("dbrp") // 17
)
// AllResourceTypes is the list of all known resource types.
@ -150,6 +152,7 @@ var AllResourceTypes = []ResourceType{
NotificationRuleResourceType, // 14
NotificationEndpointResourceType, // 15
ChecksResourceType, // 16
DBRPResourceType, // 17
// NOTE: when modifying this list, please update the swagger for components.schemas.Permission resource enum.
}
@ -167,6 +170,7 @@ var OrgResourceTypes = []ResourceType{
NotificationRuleResourceType, // 14
NotificationEndpointResourceType, // 15
ChecksResourceType, // 16
DBRPResourceType, // 17
}
// Valid checks if the resource type is a member of the ResourceType enum.
@ -194,6 +198,7 @@ func (t ResourceType) Valid() (err error) {
case NotificationRuleResourceType: // 14
case NotificationEndpointResourceType: // 15
case ChecksResourceType: // 16
case DBRPResourceType: // 17
default:
err = ErrInvalidResourceType
}
@ -201,6 +206,12 @@ func (t ResourceType) Valid() (err error) {
return err
}
type PermissionSet []Permission
func (ps PermissionSet) Allowed(p Permission) bool {
return PermissionAllowed(p, ps)
}
// Permission defines an action and a resource.
type Permission struct {
Action Action `json:"action"`

View File

@ -75,6 +75,9 @@ var authCreateFlags struct {
writeNotificationEndpointPermission bool
readNotificationEndpointPermission bool
writeDBRPPermission bool
readDBRPPermission bool
}
func authCreateCmd() *cobra.Command {
@ -118,6 +121,9 @@ func authCreateCmd() *cobra.Command {
cmd.Flags().BoolVarP(&authCreateFlags.writeCheckPermission, "write-checks", "", false, "Grants the permission to create checks")
cmd.Flags().BoolVarP(&authCreateFlags.readCheckPermission, "read-checks", "", false, "Grants the permission to read checks")
cmd.Flags().BoolVarP(&authCreateFlags.writeDBRPPermission, "write-dbrps", "", false, "Grants the permission to create database retention policy mappings")
cmd.Flags().BoolVarP(&authCreateFlags.readDBRPPermission, "read-dbrps", "", false, "Grants the permission to read database retention policy mappings")
return cmd
}
@ -216,6 +222,11 @@ func authorizationCreateF(cmd *cobra.Command, args []string) error {
writePerm: authCreateFlags.writeUserPermission,
ResourceType: platform.UsersResourceType,
},
{
readPerm: authCreateFlags.readDBRPPermission,
writePerm: authCreateFlags.writeDBRPPermission,
ResourceType: platform.DBRPResourceType,
},
}
for _, provided := range providedPerm {

View File

@ -22,6 +22,7 @@ import (
"github.com/influxdata/influxdb/v2/bolt"
"github.com/influxdata/influxdb/v2/chronograf/server"
"github.com/influxdata/influxdb/v2/cmd/influxd/inspect"
"github.com/influxdata/influxdb/v2/dbrp"
"github.com/influxdata/influxdb/v2/endpoints"
"github.com/influxdata/influxdb/v2/gather"
"github.com/influxdata/influxdb/v2/http"
@ -43,6 +44,7 @@ import (
"github.com/influxdata/influxdb/v2/query/control"
"github.com/influxdata/influxdb/v2/query/fluxlang"
"github.com/influxdata/influxdb/v2/query/stdlib/influxdata/influxdb"
"github.com/influxdata/influxdb/v2/session"
"github.com/influxdata/influxdb/v2/snowflake"
"github.com/influxdata/influxdb/v2/source"
"github.com/influxdata/influxdb/v2/storage"
@ -596,7 +598,6 @@ func (m *Launcher) run(ctx context.Context) (err error) {
authSvc platform.AuthorizationService = m.kvService
variableSvc platform.VariableService = m.kvService
sourceSvc platform.SourceService = m.kvService
sessionSvc platform.SessionService = m.kvService
dashboardSvc platform.DashboardService = m.kvService
dashboardLogSvc platform.DashboardOperationLogService = m.kvService
userLogSvc platform.UserOperationLogService = m.kvService
@ -764,6 +765,13 @@ func (m *Launcher) run(ctx context.Context) (err error) {
}
}
dbrpSvc, err := dbrp.NewService(ctx, authorizer.NewBucketService(bucketSvc, userResourceSvc), m.kvStore)
if err != nil {
return err
}
dbrpSvc = dbrp.NewAuthorizedService(dbrpSvc)
var checkSvc platform.CheckService
{
coordinator := coordinator.NewCoordinator(m.log, m.scheduler, m.executor)
@ -866,6 +874,14 @@ func (m *Launcher) run(ctx context.Context) (err error) {
flagger = f
}
var sessionSvc platform.SessionService
{
sessionSvc = session.NewService(session.NewStorage(inmem.NewSessionStore()), userSvc, userResourceSvc, authSvc, time.Duration(m.sessionLength)*time.Minute)
sessionSvc = session.NewSessionMetrics(m.reg, sessionSvc)
sessionSvc = session.NewSessionLogger(m.log.With(zap.String("service", "session")), sessionSvc)
sessionSvc = session.NewServiceController(flagger, m.kvService, sessionSvc)
}
m.apibackend = &http.APIBackend{
AssetsPath: m.assetsPath,
HTTPErrorHandler: kithttp.ErrorHandler(0),
@ -883,6 +899,7 @@ func (m *Launcher) run(ctx context.Context) (err error) {
BucketService: storage.NewBucketService(bucketSvc, m.engine),
SessionService: sessionSvc,
UserService: userSvc,
DBRPService: dbrpSvc,
OrganizationService: orgSvc,
UserResourceMappingService: userResourceSvc,
LabelService: labelSvc,
@ -985,8 +1002,21 @@ func (m *Launcher) run(ctx context.Context) (err error) {
authHTTPServer = kithttp.NewFeatureHandler(feature.NewAuthPackage(), flagger, oldHandler, newHandler, newHandler.Prefix())
}
var oldSessionHandler nethttp.Handler
var sessionHTTPServer *session.SessionHandler
{
platformHandler := http.NewPlatformHandler(m.apibackend, http.WithResourceHandler(pkgHTTPServer), http.WithResourceHandler(onboardHTTPServer), http.WithResourceHandler(authHTTPServer))
oldSessionHandler = http.NewSessionHandler(m.log.With(zap.String("handler", "old_session")), http.NewSessionBackend(m.log, m.apibackend))
sessionHTTPServer = session.NewSessionHandler(m.log.With(zap.String("handler", "session")), sessionSvc, userSvc, passwdsSvc)
}
{
platformHandler := http.NewPlatformHandler(m.apibackend,
http.WithResourceHandler(pkgHTTPServer),
http.WithResourceHandler(onboardHTTPServer),
http.WithResourceHandler(authHTTPServer),
http.WithResourceHandler(kithttp.NewFeatureHandler(feature.SessionService(), flagger, oldSessionHandler, sessionHTTPServer.SignInResourceHandler(), sessionHTTPServer.SignInResourceHandler().Prefix())),
http.WithResourceHandler(kithttp.NewFeatureHandler(feature.SessionService(), flagger, oldSessionHandler, sessionHTTPServer.SignOutResourceHandler(), sessionHTTPServer.SignOutResourceHandler().Prefix())),
)
httpLogger := m.log.With(zap.String("service", "http"))
m.httpServer.Handler = http.NewHandlerFromRegistry(

View File

@ -688,6 +688,7 @@ type LinePlusSingleStatProperties struct {
YColumn string `json:"yColumn"`
ShadeBelow bool `json:"shadeBelow"`
Position string `json:"position"`
TimeFormat string `json:"timeFormat"`
}
// XYViewProperties represents options for line, bar, step, or stacked view in Chronograf

60
dbrp/error.go Normal file
View File

@ -0,0 +1,60 @@
package dbrp
import (
"github.com/influxdata/influxdb/v2"
)
var (
// ErrInvalidDBRPID is used when the ID of the DBRP cannot be encoded.
ErrInvalidDBRPID = &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "DBRP ID is invalid",
}
// ErrDBRPNotFound is used when the specified DBRP cannot be found.
ErrDBRPNotFound = &influxdb.Error{
Code: influxdb.ENotFound,
Msg: "unable to find DBRP",
}
// ErrNotUniqueID is used when the ID of the DBRP is not unique.
ErrNotUniqueID = &influxdb.Error{
Code: influxdb.EConflict,
Msg: "ID already exists",
}
// ErrFailureGeneratingID occurs ony when the random number generator
// cannot generate an ID in MaxIDGenerationN times.
ErrFailureGeneratingID = &influxdb.Error{
Code: influxdb.EInternal,
Msg: "unable to generate valid id",
}
)
// ErrInvalidDBRP is used when a service was provided an invalid DBRP.
func ErrInvalidDBRP(err error) *influxdb.Error {
return &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "DBRP provided is invalid",
Err: err,
}
}
// ErrInternalService is used when the error comes from an internal system.
func ErrInternalService(err error) *influxdb.Error {
return &influxdb.Error{
Code: influxdb.EInternal,
Err: err,
}
}
// ErrDBRPAlreadyExists is used when there is a conflict in creating a new DBRP.
func ErrDBRPAlreadyExists(msg string) *influxdb.Error {
if msg == "" {
msg = "DBRP already exists"
}
return &influxdb.Error{
Code: influxdb.EConflict,
Msg: msg,
}
}

130
dbrp/http_client_dbrp.go Normal file
View File

@ -0,0 +1,130 @@
package dbrp
import (
"context"
"fmt"
"path"
"github.com/influxdata/influxdb/v2"
"github.com/influxdata/influxdb/v2/kit/tracing"
"github.com/influxdata/influxdb/v2/pkg/httpc"
)
var _ influxdb.DBRPMappingServiceV2 = (*Client)(nil)
// Client connects to Influx via HTTP using tokens to manage DBRPs.
type Client struct {
Client *httpc.Client
Prefix string
}
func NewClient(client *httpc.Client) *Client {
return &Client{
Client: client,
Prefix: PrefixDBRP,
}
}
func (c *Client) dbrpURL(id influxdb.ID) string {
return path.Join(c.Prefix, id.String())
}
func (c *Client) FindByID(ctx context.Context, orgID, id influxdb.ID) (*influxdb.DBRPMappingV2, error) {
span, _ := tracing.StartSpanFromContext(ctx)
defer span.Finish()
var resp getDBRPResponse
if err := c.Client.
Get(c.dbrpURL(id)).
QueryParams([2]string{"orgID", orgID.String()}).
DecodeJSON(&resp).
Do(ctx); err != nil {
return nil, err
}
return resp.Content, nil
}
func (c *Client) FindMany(ctx context.Context, filter influxdb.DBRPMappingFilterV2, opts ...influxdb.FindOptions) ([]*influxdb.DBRPMappingV2, int, error) {
span, _ := tracing.StartSpanFromContext(ctx)
defer span.Finish()
params := influxdb.FindOptionParams(opts...)
if filter.OrgID != nil {
params = append(params, [2]string{"orgID", filter.OrgID.String()})
} else {
return nil, 0, fmt.Errorf("please filter by orgID")
}
if filter.ID != nil {
params = append(params, [2]string{"id", filter.ID.String()})
}
if filter.BucketID != nil {
params = append(params, [2]string{"bucketID", filter.BucketID.String()})
}
if filter.Database != nil {
params = append(params, [2]string{"db", *filter.Database})
}
if filter.RetentionPolicy != nil {
params = append(params, [2]string{"rp", *filter.RetentionPolicy})
}
var resp getDBRPsResponse
if err := c.Client.
Get(c.Prefix).
QueryParams(params...).
DecodeJSON(&resp).
Do(ctx); err != nil {
return nil, 0, err
}
return resp.Content, len(resp.Content), nil
}
func (c *Client) Create(ctx context.Context, dbrp *influxdb.DBRPMappingV2) error {
span, _ := tracing.StartSpanFromContext(ctx)
defer span.Finish()
var newDBRP influxdb.DBRPMappingV2
if err := c.Client.
PostJSON(createDBRPRequest{
Database: dbrp.Database,
RetentionPolicy: dbrp.RetentionPolicy,
Default: dbrp.Default,
OrganizationID: dbrp.OrganizationID,
BucketID: dbrp.BucketID,
}, c.Prefix).
DecodeJSON(&newDBRP).
Do(ctx); err != nil {
return err
}
dbrp.ID = newDBRP.ID
return nil
}
func (c *Client) Update(ctx context.Context, dbrp *influxdb.DBRPMappingV2) error {
span, _ := tracing.StartSpanFromContext(ctx)
defer span.Finish()
if err := dbrp.Validate(); err != nil {
return err
}
var newDBRP influxdb.DBRPMappingV2
if err := c.Client.
PatchJSON(dbrp, c.dbrpURL(dbrp.ID)).
QueryParams([2]string{"orgID", dbrp.OrganizationID.String()}).
DecodeJSON(&newDBRP).
Do(ctx); err != nil {
return err
}
*dbrp = newDBRP
return nil
}
func (c *Client) Delete(ctx context.Context, orgID, id influxdb.ID) error {
span, _ := tracing.StartSpanFromContext(ctx)
defer span.Finish()
return c.Client.
Delete(c.dbrpURL(id)).
QueryParams([2]string{"orgID", orgID.String()}).
Do(ctx)
}

View File

@ -0,0 +1,102 @@
package dbrp_test
import (
"context"
"net/http/httptest"
"testing"
"github.com/influxdata/influxdb/v2"
"github.com/influxdata/influxdb/v2/dbrp"
"github.com/influxdata/influxdb/v2/http"
"github.com/influxdata/influxdb/v2/mock"
"github.com/influxdata/influxdb/v2/pkg/httpc"
"go.uber.org/zap/zaptest"
)
func setup(t *testing.T) (*dbrp.Client, func()) {
t.Helper()
svc := &mock.DBRPMappingServiceV2{
CreateFn: func(ctx context.Context, dbrp *influxdb.DBRPMappingV2) error {
dbrp.ID = 1
return nil
},
FindByIDFn: func(ctx context.Context, orgID, id influxdb.ID) (*influxdb.DBRPMappingV2, error) {
return &influxdb.DBRPMappingV2{
ID: id,
Database: "db",
RetentionPolicy: "rp",
Default: false,
OrganizationID: id,
BucketID: 1,
}, nil
},
FindManyFn: func(ctx context.Context, dbrp influxdb.DBRPMappingFilterV2, opts ...influxdb.FindOptions) ([]*influxdb.DBRPMappingV2, int, error) {
return []*influxdb.DBRPMappingV2{}, 0, nil
},
}
server := httptest.NewServer(dbrp.NewHTTPHandler(zaptest.NewLogger(t), svc))
client, err := httpc.New(httpc.WithAddr(server.URL), httpc.WithStatusFn(http.CheckError))
if err != nil {
t.Fatal(err)
}
dbrpClient := dbrp.NewClient(client)
dbrpClient.Prefix = ""
return dbrpClient, func() {
server.Close()
}
}
func TestClient(t *testing.T) {
t.Run("can create", func(t *testing.T) {
client, shutdown := setup(t)
defer shutdown()
if err := client.Create(context.Background(), &influxdb.DBRPMappingV2{
Database: "db",
RetentionPolicy: "rp",
Default: false,
OrganizationID: 1,
BucketID: 1,
}); err != nil {
t.Error(err)
}
})
t.Run("can read", func(t *testing.T) {
client, shutdown := setup(t)
defer shutdown()
if _, err := client.FindByID(context.Background(), 1, 1); err != nil {
t.Error(err)
}
oid := influxdb.ID(1)
if _, _, err := client.FindMany(context.Background(), influxdb.DBRPMappingFilterV2{OrgID: &oid}); err != nil {
t.Error(err)
}
})
t.Run("can update", func(t *testing.T) {
client, shutdown := setup(t)
defer shutdown()
if err := client.Update(context.Background(), &influxdb.DBRPMappingV2{
ID: 1,
Database: "db",
RetentionPolicy: "rp",
Default: false,
OrganizationID: 1,
BucketID: 1,
}); err != nil {
t.Error(err)
}
})
t.Run("can delete", func(t *testing.T) {
client, shutdown := setup(t)
defer shutdown()
if err := client.Delete(context.Background(), 1, 1); err != nil {
t.Error(err)
}
})
}

309
dbrp/http_server_dbrp.go Normal file
View File

@ -0,0 +1,309 @@
package dbrp
import (
"encoding/json"
"net/http"
"strconv"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
"github.com/influxdata/influxdb/v2"
kithttp "github.com/influxdata/influxdb/v2/kit/transport/http"
"go.uber.org/zap"
)
const (
PrefixDBRP = "/api/v2/dbrps"
)
type Handler struct {
chi.Router
api *kithttp.API
log *zap.Logger
dbrpSvc influxdb.DBRPMappingServiceV2
}
// NewHTTPHandler constructs a new http server.
func NewHTTPHandler(log *zap.Logger, dbrpSvc influxdb.DBRPMappingServiceV2) *Handler {
h := &Handler{
api: kithttp.NewAPI(kithttp.WithLog(log)),
log: log,
dbrpSvc: dbrpSvc,
}
r := chi.NewRouter()
r.Use(
middleware.Recoverer,
middleware.RequestID,
middleware.RealIP,
)
r.Route("/", func(r chi.Router) {
r.Post("/", h.handlePostDBRP)
r.Get("/", h.handleGetDBRPs)
r.Route("/{id}", func(r chi.Router) {
r.Get("/", h.handleGetDBRP)
r.Patch("/", h.handlePatchDBRP)
r.Delete("/", h.handleDeleteDBRP)
})
})
h.Router = r
return h
}
type createDBRPRequest struct {
Database string `json:"database"`
RetentionPolicy string `json:"retention_policy"`
Default bool `json:"default"`
OrganizationID influxdb.ID `json:"organization_id"`
BucketID influxdb.ID `json:"bucket_id"`
}
func (h *Handler) handlePostDBRP(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var req createDBRPRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
h.api.Err(w, r, &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "invalid json structure",
Err: err,
})
return
}
dbrp := &influxdb.DBRPMappingV2{
Database: req.Database,
RetentionPolicy: req.RetentionPolicy,
Default: req.Default,
OrganizationID: req.OrganizationID,
BucketID: req.BucketID,
}
if err := h.dbrpSvc.Create(ctx, dbrp); err != nil {
h.api.Err(w, r, err)
return
}
h.api.Respond(w, r, http.StatusCreated, dbrp)
}
type getDBRPsResponse struct {
Content []*influxdb.DBRPMappingV2 `json:"content"`
}
func (h *Handler) handleGetDBRPs(w http.ResponseWriter, r *http.Request) {
filter, err := getFilterFromHTTPRequest(r)
if err != nil {
h.api.Err(w, r, err)
return
}
dbrps, _, err := h.dbrpSvc.FindMany(r.Context(), filter)
if err != nil {
h.api.Err(w, r, err)
return
}
h.api.Respond(w, r, http.StatusOK, getDBRPsResponse{
Content: dbrps,
})
}
type getDBRPResponse struct {
Content *influxdb.DBRPMappingV2 `json:"content"`
}
func (h *Handler) handleGetDBRP(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
id := chi.URLParam(r, "id")
if id == "" {
h.api.Err(w, r, &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "url missing id",
})
return
}
var i influxdb.ID
if err := i.DecodeFromString(id); err != nil {
h.api.Err(w, r, err)
return
}
orgID, err := mustGetOrgIDFromHTTPRequest(r)
if err != nil {
h.api.Err(w, r, err)
return
}
dbrp, err := h.dbrpSvc.FindByID(ctx, *orgID, i)
if err != nil {
h.api.Err(w, r, err)
return
}
h.api.Respond(w, r, http.StatusOK, getDBRPResponse{
Content: dbrp,
})
}
func (h *Handler) handlePatchDBRP(w http.ResponseWriter, r *http.Request) {
bodyRequest := struct {
Default *bool `json:"default"`
RetentionPolicy *string `json:"retention_policy"`
}{}
ctx := r.Context()
id := chi.URLParam(r, "id")
if id == "" {
h.api.Err(w, r, &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "url missing id",
})
return
}
var i influxdb.ID
if err := i.DecodeFromString(id); err != nil {
h.api.Err(w, r, err)
return
}
orgID, err := mustGetOrgIDFromHTTPRequest(r)
if err != nil {
h.api.Err(w, r, err)
return
}
dbrp, err := h.dbrpSvc.FindByID(ctx, *orgID, i)
if err != nil {
h.api.Err(w, r, err)
return
}
if err := json.NewDecoder(r.Body).Decode(&bodyRequest); err != nil {
h.api.Err(w, r, &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "invalid json structure",
Err: err,
})
return
}
if bodyRequest.Default != nil && dbrp.Default != *bodyRequest.Default {
dbrp.Default = *bodyRequest.Default
}
if bodyRequest.RetentionPolicy != nil && *bodyRequest.RetentionPolicy != dbrp.RetentionPolicy {
dbrp.RetentionPolicy = *bodyRequest.RetentionPolicy
}
if err := h.dbrpSvc.Update(ctx, dbrp); err != nil {
h.api.Err(w, r, err)
return
}
h.api.Respond(w, r, http.StatusOK, struct {
Content *influxdb.DBRPMappingV2 `json:"content"`
}{
Content: dbrp,
})
}
func (h *Handler) handleDeleteDBRP(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
id := chi.URLParam(r, "id")
if id == "" {
h.api.Err(w, r, &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "url missing id",
})
return
}
var i influxdb.ID
if err := i.DecodeFromString(id); err != nil {
h.api.Err(w, r, err)
return
}
orgID, err := mustGetOrgIDFromHTTPRequest(r)
if err != nil {
h.api.Err(w, r, err)
return
}
if err := h.dbrpSvc.Delete(ctx, *orgID, i); err != nil {
h.api.Err(w, r, err)
return
}
w.WriteHeader(http.StatusNoContent)
}
func getFilterFromHTTPRequest(r *http.Request) (f influxdb.DBRPMappingFilterV2, err error) {
// Always provide OrgID.
f.OrgID, err = mustGetOrgIDFromHTTPRequest(r)
if err != nil {
return f, err
}
f.ID, err = getDBRPIDFromHTTPRequest(r)
if err != nil {
return f, err
}
f.BucketID, err = getBucketIDFromHTTPRequest(r)
if err != nil {
return f, err
}
rawDB := r.URL.Query().Get("db")
if rawDB != "" {
f.Database = &rawDB
}
rawRP := r.URL.Query().Get("rp")
if rawRP != "" {
f.RetentionPolicy = &rawRP
}
rawDefault := r.URL.Query().Get("default")
if rawDefault != "" {
d, err := strconv.ParseBool(rawDefault)
if err != nil {
return f, &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "invalid default parameter",
}
}
f.Default = &d
}
return f, nil
}
func getIDFromHTTPRequest(r *http.Request, key string) (*influxdb.ID, error) {
var id influxdb.ID
raw := r.URL.Query().Get(key)
if raw != "" {
if err := id.DecodeFromString(raw); err != nil {
return nil, influxdb.ErrInvalidID
}
} else {
return nil, nil
}
return &id, nil
}
func mustGetOrgIDFromHTTPRequest(r *http.Request) (*influxdb.ID, error) {
orgID, err := getIDFromHTTPRequest(r, "orgID")
if err != nil {
return nil, err
}
if orgID == nil {
return nil, influxdb.ErrOrgNotFound
}
return orgID, nil
}
func getDBRPIDFromHTTPRequest(r *http.Request) (*influxdb.ID, error) {
return getIDFromHTTPRequest(r, "id")
}
func getBucketIDFromHTTPRequest(r *http.Request) (*influxdb.ID, error) {
return getIDFromHTTPRequest(r, "bucketID")
}

View File

@ -0,0 +1,453 @@
package dbrp_test
import (
"context"
"encoding/json"
"errors"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/influxdata/influxdb/v2"
"github.com/influxdata/influxdb/v2/dbrp"
"github.com/influxdata/influxdb/v2/inmem"
"github.com/influxdata/influxdb/v2/mock"
influxdbtesting "github.com/influxdata/influxdb/v2/testing"
"go.uber.org/zap/zaptest"
)
func initHttpService(t *testing.T) (influxdb.DBRPMappingServiceV2, *httptest.Server, func()) {
t.Helper()
ctx := context.Background()
bucketSvc := mock.NewBucketService()
s := inmem.NewKVStore()
svc, err := dbrp.NewService(ctx, bucketSvc, s)
if err != nil {
t.Fatal(err)
}
server := httptest.NewServer(dbrp.NewHTTPHandler(zaptest.NewLogger(t), svc))
return svc, server, func() {
server.Close()
}
}
func Test_handlePostDBRP(t *testing.T) {
table := []struct {
Name string
ExpectedErr *influxdb.Error
ExpectedDBRP *influxdb.DBRPMappingV2
Input io.Reader
}{
{
Name: "Create valid dbrp",
Input: strings.NewReader(`{
"bucket_id": "5555f7ed2a035555",
"organization_id": "059af7ed2a034000",
"database": "mydb",
"retention_policy": "autogen",
"default": false
}`),
ExpectedDBRP: &influxdb.DBRPMappingV2{
OrganizationID: influxdbtesting.MustIDBase16("059af7ed2a034000"),
},
},
{
Name: "Create with invalid orgID",
Input: strings.NewReader(`{
"bucket_id": "5555f7ed2a035555",
"organization_id": "invalid",
"database": "mydb",
"retention_policy": "autogen",
"default": false
}`),
ExpectedErr: &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "invalid json structure",
Err: influxdb.ErrInvalidID.Err,
},
},
}
for _, tt := range table {
t.Run(tt.Name, func(t *testing.T) {
if tt.ExpectedErr != nil && tt.ExpectedDBRP != nil {
t.Error("one of those has to be set")
}
_, server, shutdown := initHttpService(t)
defer shutdown()
client := server.Client()
resp, err := client.Post(server.URL+"/", "application/json", tt.Input)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
if tt.ExpectedErr != nil {
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(b), tt.ExpectedErr.Error()) {
t.Fatal(string(b))
}
return
}
dbrp := &influxdb.DBRPMappingV2{}
if err := json.NewDecoder(resp.Body).Decode(&dbrp); err != nil {
t.Fatal(err)
}
if !dbrp.ID.Valid() {
t.Fatalf("expected invalid id, got an invalid one %s", dbrp.ID.String())
}
if dbrp.OrganizationID != tt.ExpectedDBRP.OrganizationID {
t.Fatalf("expected orgid %s got %s", tt.ExpectedDBRP.OrganizationID, dbrp.OrganizationID)
}
})
}
}
func Test_handleGetDBRPs(t *testing.T) {
table := []struct {
Name string
QueryParams string
ExpectedErr *influxdb.Error
ExpectedDBRPs []influxdb.DBRPMappingV2
}{
{
Name: "ok",
QueryParams: "orgID=059af7ed2a034000",
ExpectedDBRPs: []influxdb.DBRPMappingV2{
{
ID: influxdbtesting.MustIDBase16("1111111111111111"),
BucketID: influxdbtesting.MustIDBase16("5555f7ed2a035555"),
OrganizationID: influxdbtesting.MustIDBase16("059af7ed2a034000"),
Database: "mydb",
RetentionPolicy: "autogen",
Default: true,
},
},
},
{
Name: "invalid org",
QueryParams: "orgID=invalid",
ExpectedErr: &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "invalid ID",
},
},
{
Name: "invalid bucket",
QueryParams: "orgID=059af7ed2a034000&bucketID=invalid",
ExpectedErr: &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "invalid ID",
},
},
{
Name: "invalid default",
QueryParams: "orgID=059af7ed2a034000&default=notabool",
ExpectedErr: &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "invalid default parameter",
},
},
{
Name: "no org",
QueryParams: "default=true&retention_police=lol",
ExpectedErr: influxdb.ErrOrgNotFound,
},
{
Name: "no match",
QueryParams: "orgID=059af7ed2a034000&default=false",
ExpectedDBRPs: []influxdb.DBRPMappingV2{},
},
{
Name: "all match",
QueryParams: "orgID=059af7ed2a034000&default=true&rp=autogen&db=mydb&bucketID=5555f7ed2a035555&id=1111111111111111",
ExpectedDBRPs: []influxdb.DBRPMappingV2{
{
ID: influxdbtesting.MustIDBase16("1111111111111111"),
BucketID: influxdbtesting.MustIDBase16("5555f7ed2a035555"),
OrganizationID: influxdbtesting.MustIDBase16("059af7ed2a034000"),
Database: "mydb",
RetentionPolicy: "autogen",
Default: true,
},
},
},
}
ctx := context.Background()
for _, tt := range table {
t.Run(tt.Name, func(t *testing.T) {
if tt.ExpectedErr != nil && len(tt.ExpectedDBRPs) != 0 {
t.Error("one of those has to be set")
}
svc, server, shutdown := initHttpService(t)
defer shutdown()
if svc, ok := svc.(*dbrp.Service); ok {
svc.IDGen = mock.NewIDGenerator("1111111111111111", t)
}
db := &influxdb.DBRPMappingV2{
BucketID: influxdbtesting.MustIDBase16("5555f7ed2a035555"),
OrganizationID: influxdbtesting.MustIDBase16("059af7ed2a034000"),
Database: "mydb",
RetentionPolicy: "autogen",
Default: true,
}
if err := svc.Create(ctx, db); err != nil {
t.Fatal(err)
}
client := server.Client()
resp, err := client.Get(server.URL + "?" + tt.QueryParams)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
if tt.ExpectedErr != nil {
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(b), tt.ExpectedErr.Error()) {
t.Fatal(string(b))
}
return
}
dbrps := struct {
Content []influxdb.DBRPMappingV2 `json:"content"`
}{}
if err := json.NewDecoder(resp.Body).Decode(&dbrps); err != nil {
t.Fatal(err)
}
if len(dbrps.Content) != len(tt.ExpectedDBRPs) {
t.Fatalf("expected %d dbrps got %d", len(tt.ExpectedDBRPs), len(dbrps.Content))
}
if !cmp.Equal(tt.ExpectedDBRPs, dbrps.Content) {
t.Fatalf(cmp.Diff(tt.ExpectedDBRPs, dbrps.Content))
}
})
}
}
func Test_handlePatchDBRP(t *testing.T) {
table := []struct {
Name string
ExpectedErr *influxdb.Error
ExpectedDBRP *influxdb.DBRPMappingV2
URLSuffix string
Input io.Reader
}{
{
Name: "happy path update",
URLSuffix: "/1111111111111111?orgID=059af7ed2a034000",
Input: strings.NewReader(`{
"retention_policy": "updaterp",
"database": "wont_change"
}`),
ExpectedDBRP: &influxdb.DBRPMappingV2{
ID: influxdbtesting.MustIDBase16("1111111111111111"),
BucketID: influxdbtesting.MustIDBase16("5555f7ed2a035555"),
OrganizationID: influxdbtesting.MustIDBase16("059af7ed2a034000"),
Database: "mydb",
RetentionPolicy: "updaterp",
Default: true,
},
},
{
Name: "invalid org",
URLSuffix: "/1111111111111111?orgID=invalid",
Input: strings.NewReader(`{
"database": "updatedb"
}`),
ExpectedErr: &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "invalid ID",
},
},
{
Name: "no org",
URLSuffix: "/1111111111111111",
Input: strings.NewReader(`{
"database": "updatedb"
}`),
ExpectedErr: influxdb.ErrOrgNotFound,
},
{
Name: "not found",
URLSuffix: "/1111111111111111?orgID=059af7ed2a034001",
ExpectedErr: dbrp.ErrDBRPNotFound,
},
}
ctx := context.Background()
for _, tt := range table {
t.Run(tt.Name, func(t *testing.T) {
if tt.ExpectedErr != nil && tt.ExpectedDBRP != nil {
t.Error("one of those has to be set")
}
svc, server, shutdown := initHttpService(t)
defer shutdown()
client := server.Client()
if svc, ok := svc.(*dbrp.Service); ok {
svc.IDGen = mock.NewIDGenerator("1111111111111111", t)
}
dbrp := &influxdb.DBRPMappingV2{
BucketID: influxdbtesting.MustIDBase16("5555f7ed2a035555"),
OrganizationID: influxdbtesting.MustIDBase16("059af7ed2a034000"),
Database: "mydb",
RetentionPolicy: "autogen",
Default: true,
}
if err := svc.Create(ctx, dbrp); err != nil {
t.Fatal(err)
}
req, _ := http.NewRequest(http.MethodPatch, server.URL+tt.URLSuffix, tt.Input)
resp, err := client.Do(req)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
if tt.ExpectedErr != nil {
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(b), tt.ExpectedErr.Error()) {
t.Fatal(string(b))
}
return
}
dbrpResponse := struct {
Content *influxdb.DBRPMappingV2 `json:"content"`
}{}
if err := json.NewDecoder(resp.Body).Decode(&dbrpResponse); err != nil {
t.Fatal(err)
}
if !cmp.Equal(tt.ExpectedDBRP, dbrpResponse.Content) {
t.Fatalf(cmp.Diff(tt.ExpectedDBRP, dbrpResponse.Content))
}
})
}
}
func Test_handleDeleteDBRP(t *testing.T) {
table := []struct {
Name string
URLSuffix string
ExpectedErr *influxdb.Error
ExpectStillExists bool
}{
{
Name: "delete",
URLSuffix: "/1111111111111111?orgID=059af7ed2a034000",
},
{
Name: "invalid org",
URLSuffix: "/1111111111111111?orgID=invalid",
ExpectedErr: &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "invalid ID",
},
},
{
Name: "no org",
URLSuffix: "/1111111111111111",
ExpectedErr: influxdb.ErrOrgNotFound,
},
{
// Not found is not an error for Delete.
Name: "not found",
URLSuffix: "/1111111111111111?orgID=059af7ed2a034001",
ExpectStillExists: true,
},
}
ctx := context.Background()
for _, tt := range table {
t.Run(tt.Name, func(t *testing.T) {
svc, server, shutdown := initHttpService(t)
defer shutdown()
client := server.Client()
d := &influxdb.DBRPMappingV2{
ID: influxdbtesting.MustIDBase16("1111111111111111"),
BucketID: influxdbtesting.MustIDBase16("5555f7ed2a035555"),
OrganizationID: influxdbtesting.MustIDBase16("059af7ed2a034000"),
Database: "mydb",
RetentionPolicy: "autogen",
Default: true,
}
if err := svc.Create(ctx, d); err != nil {
t.Fatal(err)
}
req, _ := http.NewRequest(http.MethodDelete, server.URL+tt.URLSuffix, nil)
resp, err := client.Do(req)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
if tt.ExpectedErr != nil {
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(b), tt.ExpectedErr.Error()) {
t.Fatal(string(b))
}
return
}
if resp.StatusCode != http.StatusNoContent {
t.Fatalf("expected status code %d, got %d", http.StatusNoContent, resp.StatusCode)
}
gotDBRP, err := svc.FindByID(ctx, influxdbtesting.MustIDBase16("059af7ed2a034000"), influxdbtesting.MustIDBase16("1111111111111111"))
if tt.ExpectStillExists {
if err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(d, gotDBRP); diff != "" {
t.Fatal(diff)
}
} else {
if err == nil {
t.Fatal("expected error got none")
}
if !errors.Is(err, dbrp.ErrDBRPNotFound) {
t.Fatalf("expected err dbrp not found, got %s", err)
}
}
})
}
}

54
dbrp/middleware_auth.go Normal file
View File

@ -0,0 +1,54 @@
package dbrp
import (
"context"
"github.com/influxdata/influxdb/v2"
"github.com/influxdata/influxdb/v2/authorizer"
)
var _ influxdb.DBRPMappingServiceV2 = (*AuthorizedService)(nil)
type AuthorizedService struct {
influxdb.DBRPMappingServiceV2
}
func NewAuthorizedService(s influxdb.DBRPMappingServiceV2) *AuthorizedService {
return &AuthorizedService{DBRPMappingServiceV2: s}
}
func (svc AuthorizedService) FindByID(ctx context.Context, orgID, id influxdb.ID) (*influxdb.DBRPMappingV2, error) {
if _, _, err := authorizer.AuthorizeRead(ctx, influxdb.DBRPResourceType, id, orgID); err != nil {
return nil, err
}
return svc.DBRPMappingServiceV2.FindByID(ctx, orgID, id)
}
func (svc AuthorizedService) FindMany(ctx context.Context, filter influxdb.DBRPMappingFilterV2, opts ...influxdb.FindOptions) ([]*influxdb.DBRPMappingV2, int, error) {
dbrps, _, err := svc.DBRPMappingServiceV2.FindMany(ctx, filter, opts...)
if err != nil {
return nil, 0, err
}
return authorizer.AuthorizeFindDBRPs(ctx, dbrps)
}
func (svc AuthorizedService) Create(ctx context.Context, t *influxdb.DBRPMappingV2) error {
if _, _, err := authorizer.AuthorizeCreate(ctx, influxdb.DBRPResourceType, t.OrganizationID); err != nil {
return err
}
return svc.DBRPMappingServiceV2.Create(ctx, t)
}
func (svc AuthorizedService) Update(ctx context.Context, u *influxdb.DBRPMappingV2) error {
if _, _, err := authorizer.AuthorizeWrite(ctx, influxdb.DBRPResourceType, u.ID, u.OrganizationID); err != nil {
return err
}
return svc.DBRPMappingServiceV2.Update(ctx, u)
}
func (svc AuthorizedService) Delete(ctx context.Context, orgID, id influxdb.ID) error {
if _, _, err := authorizer.AuthorizeWrite(ctx, influxdb.DBRPResourceType, id, orgID); err != nil {
return err
}
return svc.DBRPMappingServiceV2.Delete(ctx, orgID, id)
}

View File

@ -0,0 +1,591 @@
package dbrp_test
import (
"context"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/influxdata/influxdb/v2"
influxdbcontext "github.com/influxdata/influxdb/v2/context"
"github.com/influxdata/influxdb/v2/dbrp"
"github.com/influxdata/influxdb/v2/mock"
influxdbtesting "github.com/influxdata/influxdb/v2/testing"
)
func TestAuth_FindByID(t *testing.T) {
type fields struct {
service influxdb.DBRPMappingServiceV2
}
type args struct {
orgID influxdb.ID
id influxdb.ID
permission influxdb.Permission
}
type wants struct {
err error
}
tests := []struct {
name string
fields fields
args args
wants wants
}{
{
name: "authorized to access id by org id",
fields: fields{
service: &mock.DBRPMappingServiceV2{},
},
args: args{
permission: influxdb.Permission{
Action: "read",
Resource: influxdb.Resource{
Type: influxdb.DBRPResourceType,
OrgID: influxdbtesting.IDPtr(1),
},
},
id: 1,
orgID: 1,
},
wants: wants{
err: nil,
},
},
{
name: "authorized to access id by id",
fields: fields{
service: &mock.DBRPMappingServiceV2{},
},
args: args{
permission: influxdb.Permission{
Action: "read",
Resource: influxdb.Resource{
Type: influxdb.DBRPResourceType,
ID: influxdbtesting.IDPtr(1),
},
},
id: 1,
orgID: 1,
},
wants: wants{
err: nil,
},
},
{
name: "unauthorized to access id by org id",
fields: fields{
service: &mock.DBRPMappingServiceV2{},
},
args: args{
permission: influxdb.Permission{
Action: "read",
Resource: influxdb.Resource{
Type: influxdb.DBRPResourceType,
OrgID: influxdbtesting.IDPtr(1),
},
},
id: 1,
orgID: 2,
},
wants: wants{
err: &influxdb.Error{
Msg: "read:orgs/0000000000000002/dbrp/0000000000000001 is unauthorized",
Code: influxdb.EUnauthorized,
},
},
},
{
name: "unauthorized to access id by id",
fields: fields{
service: &mock.DBRPMappingServiceV2{},
},
args: args{
permission: influxdb.Permission{
Action: "read",
Resource: influxdb.Resource{
Type: influxdb.DBRPResourceType,
ID: influxdbtesting.IDPtr(2),
},
},
id: 1,
orgID: 2,
},
wants: wants{
err: &influxdb.Error{
Msg: "read:orgs/0000000000000002/dbrp/0000000000000001 is unauthorized",
Code: influxdb.EUnauthorized,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := dbrp.NewAuthorizedService(tt.fields.service)
ctx := context.Background()
ctx = influxdbcontext.SetAuthorizer(ctx, mock.NewMockAuthorizer(false, []influxdb.Permission{tt.args.permission}))
_, err := s.FindByID(ctx, tt.args.orgID, tt.args.id)
influxdbtesting.ErrorsEqual(t, err, tt.wants.err)
})
}
}
func TestAuth_FindMany(t *testing.T) {
type fields struct {
service influxdb.DBRPMappingServiceV2
}
type args struct {
filter influxdb.DBRPMappingFilterV2
permissions []influxdb.Permission
}
type wants struct {
err error
ms []*influxdb.DBRPMappingV2
}
tests := []struct {
name string
fields fields
args args
wants wants
}{
{
name: "no result",
fields: fields{
service: &mock.DBRPMappingServiceV2{
FindManyFn: func(ctx context.Context, dbrp influxdb.DBRPMappingFilterV2, opts ...influxdb.FindOptions) ([]*influxdb.DBRPMappingV2, int, error) {
return []*influxdb.DBRPMappingV2{
{
ID: 1,
OrganizationID: 1,
BucketID: 1,
},
{
ID: 2,
OrganizationID: 1,
BucketID: 2,
},
{
ID: 3,
OrganizationID: 2,
BucketID: 3,
},
{
ID: 4,
OrganizationID: 3,
BucketID: 4,
},
}, 4, nil
},
},
},
args: args{
permissions: []influxdb.Permission{{
Action: "read",
Resource: influxdb.Resource{
Type: influxdb.DBRPResourceType,
OrgID: influxdbtesting.IDPtr(42),
},
}},
filter: influxdb.DBRPMappingFilterV2{},
},
wants: wants{
err: nil,
ms: []*influxdb.DBRPMappingV2{},
},
},
{
name: "partial",
fields: fields{
service: &mock.DBRPMappingServiceV2{
FindManyFn: func(ctx context.Context, dbrp influxdb.DBRPMappingFilterV2, opts ...influxdb.FindOptions) ([]*influxdb.DBRPMappingV2, int, error) {
return []*influxdb.DBRPMappingV2{
{
ID: 1,
OrganizationID: 1,
BucketID: 1,
},
{
ID: 2,
OrganizationID: 1,
BucketID: 2,
},
{
ID: 3,
OrganizationID: 2,
BucketID: 3,
},
{
ID: 4,
OrganizationID: 3,
BucketID: 4,
},
}, 4, nil
},
},
},
args: args{
permissions: []influxdb.Permission{{
Action: "read",
Resource: influxdb.Resource{
Type: influxdb.DBRPResourceType,
OrgID: influxdbtesting.IDPtr(1),
},
}},
filter: influxdb.DBRPMappingFilterV2{},
},
wants: wants{
err: nil,
ms: []*influxdb.DBRPMappingV2{
{
ID: 1,
OrganizationID: 1,
BucketID: 1,
},
{
ID: 2,
OrganizationID: 1,
BucketID: 2,
},
},
},
},
{
name: "all",
fields: fields{
service: &mock.DBRPMappingServiceV2{
FindManyFn: func(ctx context.Context, dbrp influxdb.DBRPMappingFilterV2, opts ...influxdb.FindOptions) ([]*influxdb.DBRPMappingV2, int, error) {
return []*influxdb.DBRPMappingV2{
{
ID: 1,
OrganizationID: 1,
BucketID: 1,
},
{
ID: 2,
OrganizationID: 1,
BucketID: 2,
},
{
ID: 3,
OrganizationID: 2,
BucketID: 3,
},
{
ID: 4,
OrganizationID: 3,
BucketID: 4,
},
}, 4, nil
},
},
},
args: args{
permissions: []influxdb.Permission{
{
Action: "read",
Resource: influxdb.Resource{
Type: influxdb.DBRPResourceType,
OrgID: influxdbtesting.IDPtr(1),
},
},
{
Action: "read",
Resource: influxdb.Resource{
Type: influxdb.DBRPResourceType,
OrgID: influxdbtesting.IDPtr(2),
},
},
{
Action: "read",
Resource: influxdb.Resource{
Type: influxdb.DBRPResourceType,
OrgID: influxdbtesting.IDPtr(3),
},
},
},
filter: influxdb.DBRPMappingFilterV2{},
},
wants: wants{
err: nil,
ms: []*influxdb.DBRPMappingV2{
{
ID: 1,
OrganizationID: 1,
BucketID: 1,
},
{
ID: 2,
OrganizationID: 1,
BucketID: 2,
},
{
ID: 3,
OrganizationID: 2,
BucketID: 3,
},
{
ID: 4,
OrganizationID: 3,
BucketID: 4,
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := dbrp.NewAuthorizedService(tt.fields.service)
ctx := context.Background()
ctx = influxdbcontext.SetAuthorizer(ctx, mock.NewMockAuthorizer(false, tt.args.permissions))
gots, ngots, err := s.FindMany(ctx, tt.args.filter)
if ngots != len(gots) {
t.Errorf("got wrong number back")
}
influxdbtesting.ErrorsEqual(t, err, tt.wants.err)
if diff := cmp.Diff(tt.wants.ms, gots, influxdbtesting.DBRPMappingCmpOptionsV2...); diff != "" {
t.Errorf("unexpected result -want/+got:\n\t%s", diff)
}
})
}
}
func TestAuth_Create(t *testing.T) {
type fields struct {
service influxdb.DBRPMappingServiceV2
}
type args struct {
m influxdb.DBRPMappingV2
permission influxdb.Permission
}
type wants struct {
err error
}
tests := []struct {
name string
fields fields
args args
wants wants
}{
{
name: "authorized",
fields: fields{
service: &mock.DBRPMappingServiceV2{},
},
args: args{
m: influxdb.DBRPMappingV2{
ID: 1,
OrganizationID: 1,
},
permission: influxdb.Permission{
Action: "write",
Resource: influxdb.Resource{
Type: influxdb.DBRPResourceType,
OrgID: influxdbtesting.IDPtr(1),
},
},
},
wants: wants{
err: nil,
},
},
{
name: "unauthorized",
fields: fields{
service: &mock.DBRPMappingServiceV2{},
},
args: args{
m: influxdb.DBRPMappingV2{
ID: 1,
OrganizationID: 1,
},
permission: influxdb.Permission{
Action: "read",
Resource: influxdb.Resource{
Type: influxdb.DBRPResourceType,
OrgID: influxdbtesting.IDPtr(1),
},
},
},
wants: wants{
err: &influxdb.Error{
Msg: "write:orgs/0000000000000001/dbrp is unauthorized",
Code: influxdb.EUnauthorized,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := dbrp.NewAuthorizedService(tt.fields.service)
ctx := context.Background()
ctx = influxdbcontext.SetAuthorizer(ctx, mock.NewMockAuthorizer(false, []influxdb.Permission{tt.args.permission}))
err := s.Create(ctx, &tt.args.m)
influxdbtesting.ErrorsEqual(t, err, tt.wants.err)
})
}
}
func TestAuth_Update(t *testing.T) {
type fields struct {
service influxdb.DBRPMappingServiceV2
}
type args struct {
orgID influxdb.ID
id influxdb.ID
permission influxdb.Permission
}
type wants struct {
err error
}
tests := []struct {
name string
fields fields
args args
wants wants
}{
{
name: "authorized",
fields: fields{
service: &mock.DBRPMappingServiceV2{},
},
args: args{
permission: influxdb.Permission{
Action: "write",
Resource: influxdb.Resource{
Type: influxdb.DBRPResourceType,
OrgID: influxdbtesting.IDPtr(1),
},
},
id: 1,
orgID: 1,
},
wants: wants{
err: nil,
},
},
{
name: "unauthorized",
fields: fields{
service: &mock.DBRPMappingServiceV2{},
},
args: args{
permission: influxdb.Permission{
Action: "read",
Resource: influxdb.Resource{
Type: influxdb.DBRPResourceType,
OrgID: influxdbtesting.IDPtr(1),
},
},
id: 1,
orgID: 1,
},
wants: wants{
err: &influxdb.Error{
Msg: "write:orgs/0000000000000001/dbrp/0000000000000001 is unauthorized",
Code: influxdb.EUnauthorized,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := dbrp.NewAuthorizedService(tt.fields.service)
ctx := context.Background()
ctx = influxdbcontext.SetAuthorizer(ctx, mock.NewMockAuthorizer(false, []influxdb.Permission{tt.args.permission}))
// Does not matter how we update, we only need to check auth.
err := s.Update(ctx, &influxdb.DBRPMappingV2{ID: tt.args.id, OrganizationID: tt.args.orgID, BucketID: 1})
influxdbtesting.ErrorsEqual(t, err, tt.wants.err)
})
}
}
func TestAuth_Delete(t *testing.T) {
type fields struct {
service influxdb.DBRPMappingServiceV2
}
type args struct {
orgID influxdb.ID
id influxdb.ID
permission influxdb.Permission
}
type wants struct {
err error
}
tests := []struct {
name string
fields fields
args args
wants wants
}{
{
name: "authorized",
fields: fields{
service: &mock.DBRPMappingServiceV2{},
},
args: args{
permission: influxdb.Permission{
Action: "write",
Resource: influxdb.Resource{
Type: influxdb.DBRPResourceType,
OrgID: influxdbtesting.IDPtr(1),
},
},
id: 1,
orgID: 1,
},
wants: wants{
err: nil,
},
},
{
name: "unauthorized",
fields: fields{
service: &mock.DBRPMappingServiceV2{},
},
args: args{
permission: influxdb.Permission{
Action: "read",
Resource: influxdb.Resource{
Type: influxdb.DBRPResourceType,
OrgID: influxdbtesting.IDPtr(1),
},
},
id: 1,
orgID: 1,
},
wants: wants{
err: &influxdb.Error{
Msg: "write:orgs/0000000000000001/dbrp/0000000000000001 is unauthorized",
Code: influxdb.EUnauthorized,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := dbrp.NewAuthorizedService(tt.fields.service)
ctx := context.Background()
ctx = influxdbcontext.SetAuthorizer(ctx, mock.NewMockAuthorizer(false, []influxdb.Permission{tt.args.permission}))
err := s.Delete(ctx, tt.args.orgID, tt.args.id)
influxdbtesting.ErrorsEqual(t, err, tt.wants.err)
})
}
}

514
dbrp/service.go Normal file
View File

@ -0,0 +1,514 @@
package dbrp
// The DBRP Mapping `Service` maps database, retention policy pairs to buckets.
// Every `DBRPMapping` stored is scoped to an organization ID.
// The service must ensure the following invariants are valid at any time:
// - each orgID, database, retention policy triple must be unique;
// - for each orgID and database there must exist one and only one default mapping (`mapping.Default` set to `true`).
// The service does so using three kv buckets:
// - one for storing mappings;
// - one for storing an index of mappings by orgID and database;
// - one for storing the current default mapping for an orgID and a database.
//
// On *create*, the service creates the mapping.
// If another mapping with the same orgID, database, and retention policy exists, it fails.
// If the mapping is the first one for the specified orgID-database couple, it will be the default one.
//
// On *find*, the service find mappings.
// Every mapping returned uses the kv bucket where the default is specified to update the `mapping.Default` field.
//
// On *update*, the service updates the mapping.
// If the update causes another bucket to have the same orgID, database, and retention policy, it fails.
// If the update unsets `mapping.Default`, the first mapping found is set as default.
//
// On *delete*, the service updates the mapping.
// If the deletion deletes the default mapping, the first mapping found is set as default.
import (
"bytes"
"context"
"encoding/json"
"fmt"
"github.com/influxdata/influxdb/v2"
"github.com/influxdata/influxdb/v2/kv"
"github.com/influxdata/influxdb/v2/snowflake"
)
var (
bucket = []byte("dbrpv1")
indexBucket = []byte("dbrpbyorganddbindexv1")
defaultBucket = []byte("dbrpdefaultv1")
)
var _ influxdb.DBRPMappingServiceV2 = (*AuthorizedService)(nil)
type Service struct {
store kv.Store
IDGen influxdb.IDGenerator
bucketSvc influxdb.BucketService
byOrgAndDatabase *kv.Index
}
func indexForeignKey(dbrp influxdb.DBRPMappingV2) []byte {
return composeForeignKey(dbrp.OrganizationID, dbrp.Database)
}
func composeForeignKey(orgID influxdb.ID, db string) []byte {
encID, _ := orgID.Encode()
key := make([]byte, len(encID)+len(db))
copy(key, encID)
copy(key[len(encID):], db)
return key
}
func NewService(ctx context.Context, bucketSvc influxdb.BucketService, st kv.Store) (influxdb.DBRPMappingServiceV2, error) {
if err := st.Update(ctx, func(tx kv.Tx) error {
_, err := tx.Bucket(bucket)
if err != nil {
return err
}
_, err = tx.Bucket(indexBucket)
if err != nil {
return err
}
_, err = tx.Bucket(defaultBucket)
return err
}); err != nil {
return nil, err
}
return &Service{
store: st,
IDGen: snowflake.NewDefaultIDGenerator(),
bucketSvc: bucketSvc,
byOrgAndDatabase: kv.NewIndex(kv.NewIndexMapping(bucket, indexBucket, func(v []byte) ([]byte, error) {
var dbrp influxdb.DBRPMappingV2
if err := json.Unmarshal(v, &dbrp); err != nil {
return nil, err
}
return indexForeignKey(dbrp), nil
}), kv.WithIndexReadPathEnabled),
}, nil
}
// getDefault gets the default mapping ID inside of a transaction.
func (s *Service) getDefault(tx kv.Tx, compKey []byte) ([]byte, error) {
b, err := tx.Bucket(defaultBucket)
if err != nil {
return nil, err
}
defID, err := b.Get(compKey)
if err != nil {
return nil, err
}
return defID, nil
}
// getDefaultID returns the default mapping ID for the given orgID and db.
func (s *Service) getDefaultID(tx kv.Tx, compKey []byte) (influxdb.ID, error) {
defID, err := s.getDefault(tx, compKey)
if err != nil {
return 0, err
}
id := new(influxdb.ID)
if err := id.Decode(defID); err != nil {
return 0, err
}
return *id, nil
}
// isDefault tells whether a mapping is the default one.
func (s *Service) isDefault(tx kv.Tx, compKey []byte, id []byte) (bool, error) {
defID, err := s.getDefault(tx, compKey)
if kv.IsNotFound(err) {
return false, nil
}
if err != nil {
return false, err
}
return bytes.Equal(id, defID), nil
}
// isDefaultSet tells if there is a default mapping for the given composite key.
func (s *Service) isDefaultSet(tx kv.Tx, compKey []byte) (bool, error) {
b, err := tx.Bucket(defaultBucket)
if err != nil {
return false, ErrInternalService(err)
}
_, err = b.Get(compKey)
if kv.IsNotFound(err) {
return false, nil
}
if err != nil {
return false, ErrInternalService(err)
}
return true, nil
}
// setAsDefault sets the given id as default for the given composite key.
func (s *Service) setAsDefault(tx kv.Tx, compKey []byte, id []byte) error {
b, err := tx.Bucket(defaultBucket)
if err != nil {
return ErrInternalService(err)
}
if err := b.Put(compKey, id); err != nil {
return ErrInternalService(err)
}
return nil
}
// unsetDefault un-sets the default for the given composite key.
// Useful when a db/rp pair does not exist anymore.
func (s *Service) unsetDefault(tx kv.Tx, compKey []byte) error {
b, err := tx.Bucket(defaultBucket)
if err != nil {
return ErrInternalService(err)
}
if err = b.Delete(compKey); err != nil {
return ErrInternalService(err)
}
return nil
}
// getFirstBut returns the first element in the db/rp index (not accounting for the `skipID`).
// If the length of the returned ID is 0, it means no element was found.
// The skip value is useful, for instance, if one wants to delete an element based on the result of this operation.
func (s *Service) getFirstBut(tx kv.Tx, compKey []byte, skipID []byte) ([]byte, error) {
stop := fmt.Errorf("stop")
var next []byte
if err := s.byOrgAndDatabase.Walk(context.Background(), tx, compKey, func(k, v []byte) error {
if bytes.Equal(skipID, k) {
return nil
}
next = k
return stop
}); err != nil && err != stop {
return nil, ErrInternalService(err)
}
return next, nil
}
// isDBRPUnique verifies if the triple orgID-database-retention-policy is unique.
func (s *Service) isDBRPUnique(ctx context.Context, m influxdb.DBRPMappingV2) error {
return s.store.View(ctx, func(tx kv.Tx) error {
return s.byOrgAndDatabase.Walk(ctx, tx, composeForeignKey(m.OrganizationID, m.Database), func(k, v []byte) error {
dbrp := &influxdb.DBRPMappingV2{}
if err := json.Unmarshal(v, dbrp); err != nil {
return ErrInternalService(err)
}
if dbrp.ID == m.ID {
// Corner case.
// This is the very same DBRP, just skip it!
return nil
}
if dbrp.RetentionPolicy == m.RetentionPolicy {
return ErrDBRPAlreadyExists("another DBRP mapping with same orgID, db, and rp exists")
}
return nil
})
})
}
// FindBy returns the mapping for the given ID.
func (s *Service) FindByID(ctx context.Context, orgID, id influxdb.ID) (*influxdb.DBRPMappingV2, error) {
encodedID, err := id.Encode()
if err != nil {
return nil, ErrInvalidDBRPID
}
m := &influxdb.DBRPMappingV2{}
if err := s.store.View(ctx, func(tx kv.Tx) error {
bucket, err := tx.Bucket(bucket)
if err != nil {
return ErrInternalService(err)
}
b, err := bucket.Get(encodedID)
if err != nil {
return ErrDBRPNotFound
}
if err := json.Unmarshal(b, m); err != nil {
return ErrInternalService(err)
}
// If the given orgID is wrong, it is as if we did not found a mapping scoped to this org.
if m.OrganizationID != orgID {
return ErrDBRPNotFound
}
// Update the default value for this mapping.
m.Default, err = s.isDefault(tx, indexForeignKey(*m), encodedID)
if err != nil {
return ErrInternalService(err)
}
return nil
}); err != nil {
return nil, err
}
return m, nil
}
// FindMany returns a list of mappings that match filter and the total count of matching dbrp mappings.
// TODO(affo): find a smart way to apply FindOptions to a list of items.
func (s *Service) FindMany(ctx context.Context, filter influxdb.DBRPMappingFilterV2, opts ...influxdb.FindOptions) ([]*influxdb.DBRPMappingV2, int, error) {
// Memoize default IDs.
defs := make(map[string]*influxdb.ID)
get := func(tx kv.Tx, orgID influxdb.ID, db string) (*influxdb.ID, error) {
k := orgID.String() + db
if _, ok := defs[k]; !ok {
id, err := s.getDefaultID(tx, composeForeignKey(orgID, db))
if kv.IsNotFound(err) {
// Still need to store a not-found result.
defs[k] = nil
} else if err != nil {
return nil, err
} else {
defs[k] = &id
}
}
return defs[k], nil
}
ms := []*influxdb.DBRPMappingV2{}
add := func(tx kv.Tx) func(k, v []byte) error {
return func(k, v []byte) error {
m := influxdb.DBRPMappingV2{}
if err := json.Unmarshal(v, &m); err != nil {
return ErrInternalService(err)
}
// Updating the Default field must be done before filtering.
defID, err := get(tx, m.OrganizationID, m.Database)
if err != nil {
return ErrInternalService(err)
}
m.Default = m.ID == *defID
if filterFunc(&m, filter) {
ms = append(ms, &m)
}
return nil
}
}
return ms, len(ms), s.store.View(ctx, func(tx kv.Tx) error {
// Optimized path, use index.
if orgID := filter.OrgID; orgID != nil {
// The index performs a prefix search.
// The foreign key is `orgID + db`.
// If you want to look by orgID only, just pass orgID as prefix.
db := ""
if filter.Database != nil {
db = *filter.Database
}
compKey := composeForeignKey(*orgID, db)
if len(db) > 0 {
// Even more optimized, looking for the default given an orgID and database.
// No walking index needed.
if def := filter.Default; def != nil && *def {
defID, err := s.getDefault(tx, compKey)
if kv.IsNotFound(err) {
return nil
}
if err != nil {
return ErrInternalService(err)
}
bucket, err := tx.Bucket(bucket)
if err != nil {
return ErrInternalService(err)
}
v, err := bucket.Get(defID)
if err != nil {
return ErrInternalService(err)
}
return add(tx)(defID, v)
}
}
return s.byOrgAndDatabase.Walk(ctx, tx, compKey, add(tx))
}
bucket, err := tx.Bucket(bucket)
if err != nil {
return ErrInternalService(err)
}
cur, err := bucket.Cursor()
if err != nil {
return ErrInternalService(err)
}
for k, v := cur.First(); k != nil; k, v = cur.Next() {
if err := add(tx)(k, v); err != nil {
return err
}
}
return nil
})
}
// Create creates a new mapping.
// If another mapping with same organization ID, database, and retention policy exists, an error is returned.
// If the mapping already contains a valid ID, that one is used for storing the mapping.
func (s *Service) Create(ctx context.Context, dbrp *influxdb.DBRPMappingV2) error {
if !dbrp.ID.Valid() {
dbrp.ID = s.IDGen.ID()
}
if err := dbrp.Validate(); err != nil {
return ErrInvalidDBRP(err)
}
if _, err := s.bucketSvc.FindBucketByID(ctx, dbrp.BucketID); err != nil {
return err
}
// If a dbrp with this particular ID already exists an error is returned.
if _, err := s.FindByID(ctx, dbrp.OrganizationID, dbrp.ID); err == nil {
return ErrDBRPAlreadyExists("dbrp already exist for this particular ID. If you are trying an update use the right function .Update")
}
// If a dbrp with this orgID, db, and rp exists an error is returned.
if err := s.isDBRPUnique(ctx, *dbrp); err != nil {
return err
}
encodedID, err := dbrp.ID.Encode()
if err != nil {
return ErrInvalidDBRPID
}
b, err := json.Marshal(dbrp)
if err != nil {
return ErrInternalService(err)
}
return s.store.Update(ctx, func(tx kv.Tx) error {
bucket, err := tx.Bucket(bucket)
if err != nil {
return ErrInternalService(err)
}
if err := bucket.Put(encodedID, b); err != nil {
return ErrInternalService(err)
}
compKey := indexForeignKey(*dbrp)
if err := s.byOrgAndDatabase.Insert(tx, compKey, encodedID); err != nil {
return err
}
defSet, err := s.isDefaultSet(tx, compKey)
if err != nil {
return err
}
if !defSet {
dbrp.Default = true
}
if dbrp.Default {
if err := s.setAsDefault(tx, compKey, encodedID); err != nil {
return err
}
}
return nil
})
}
// Updates a mapping.
// If another mapping with same organization ID, database, and retention policy exists, an error is returned.
// Un-setting `Default` for a mapping will cause the first one to become the default.
func (s *Service) Update(ctx context.Context, dbrp *influxdb.DBRPMappingV2) error {
if err := dbrp.Validate(); err != nil {
return ErrInvalidDBRP(err)
}
oldDBRP, err := s.FindByID(ctx, dbrp.OrganizationID, dbrp.ID)
if err != nil {
return ErrDBRPNotFound
}
// Overwrite fields that cannot change.
dbrp.ID = oldDBRP.ID
dbrp.OrganizationID = oldDBRP.OrganizationID
dbrp.BucketID = oldDBRP.BucketID
dbrp.Database = oldDBRP.Database
// If a dbrp with this orgID, db, and rp exists an error is returned.
if err := s.isDBRPUnique(ctx, *dbrp); err != nil {
return err
}
encodedID, err := dbrp.ID.Encode()
if err != nil {
return ErrInternalService(err)
}
b, err := json.Marshal(dbrp)
if err != nil {
return ErrInternalService(err)
}
return s.store.Update(ctx, func(tx kv.Tx) error {
bucket, err := tx.Bucket(bucket)
if err != nil {
return ErrInternalService(err)
}
if err := bucket.Put(encodedID, b); err != nil {
return err
}
compKey := indexForeignKey(*dbrp)
if dbrp.Default {
err = s.setAsDefault(tx, compKey, encodedID)
} else if oldDBRP.Default {
// This means default was unset.
// Need to find a new default.
first, ferr := s.getFirstBut(tx, compKey, encodedID)
if ferr != nil {
return ferr
}
if len(first) > 0 {
err = s.setAsDefault(tx, compKey, first)
}
// If no first was found, then this will remain the default.
}
return err
})
}
// Delete removes a mapping.
// Deleting a mapping that does not exists is not an error.
// Deleting the default mapping will cause the first one (if any) to become the default.
func (s *Service) Delete(ctx context.Context, orgID, id influxdb.ID) error {
dbrp, err := s.FindByID(ctx, orgID, id)
if err != nil {
return nil
}
encodedID, err := id.Encode()
if err != nil {
return ErrInternalService(err)
}
return s.store.Update(ctx, func(tx kv.Tx) error {
bucket, err := tx.Bucket(bucket)
if err != nil {
return ErrInternalService(err)
}
compKey := indexForeignKey(*dbrp)
if err := bucket.Delete(encodedID); err != nil {
return err
}
if err := s.byOrgAndDatabase.Delete(tx, compKey, encodedID); err != nil {
return ErrInternalService(err)
}
// If this was the default, we need to set a new default.
var derr error
if dbrp.Default {
first, err := s.getFirstBut(tx, compKey, encodedID)
if err != nil {
return err
}
if len(first) > 0 {
derr = s.setAsDefault(tx, compKey, first)
} else {
// This means no other mapping is in the index.
// Unset the default
derr = s.unsetDefault(tx, compKey)
}
}
return derr
})
}
// filterFunc is capable to validate if the dbrp is valid from a given filter.
// it runs true if the filtering data are contained in the dbrp.
func filterFunc(dbrp *influxdb.DBRPMappingV2, filter influxdb.DBRPMappingFilterV2) bool {
return (filter.ID == nil || (*filter.ID) == dbrp.ID) &&
(filter.OrgID == nil || (*filter.OrgID) == dbrp.OrganizationID) &&
(filter.BucketID == nil || (*filter.BucketID) == dbrp.BucketID) &&
(filter.Database == nil || (*filter.Database) == dbrp.Database) &&
(filter.RetentionPolicy == nil || (*filter.RetentionPolicy) == dbrp.RetentionPolicy) &&
(filter.Default == nil || (*filter.Default) == dbrp.Default)
}

80
dbrp/service_test.go Normal file
View File

@ -0,0 +1,80 @@
package dbrp_test
import (
"context"
"errors"
"fmt"
"io/ioutil"
"os"
"testing"
"github.com/influxdata/influxdb/v2"
"github.com/influxdata/influxdb/v2/bolt"
"github.com/influxdata/influxdb/v2/dbrp"
"github.com/influxdata/influxdb/v2/kv"
"github.com/influxdata/influxdb/v2/mock"
itesting "github.com/influxdata/influxdb/v2/testing"
"go.uber.org/zap/zaptest"
)
func NewTestBoltStore(t *testing.T) (kv.Store, func(), error) {
f, err := ioutil.TempFile("", "influxdata-bolt-")
if err != nil {
return nil, nil, errors.New("unable to open temporary boltdb file")
}
f.Close()
path := f.Name()
s := bolt.NewKVStore(zaptest.NewLogger(t), path)
if err := s.Open(context.Background()); err != nil {
return nil, nil, err
}
close := func() {
s.Close()
os.Remove(path)
}
return s, close, nil
}
func initDBRPMappingService(f itesting.DBRPMappingFieldsV2, t *testing.T) (influxdb.DBRPMappingServiceV2, func()) {
s, closeStore, err := NewTestBoltStore(t)
if err != nil {
t.Fatalf("failed to create new bolt kv store: %v", err)
}
ks := kv.NewService(zaptest.NewLogger(t), s)
if err := ks.Initialize(context.Background()); err != nil {
t.Fatal(err)
}
if f.BucketSvc == nil {
f.BucketSvc = &mock.BucketService{
FindBucketByIDFn: func(ctx context.Context, id influxdb.ID) (*influxdb.Bucket, error) {
// always find a bucket.
return &influxdb.Bucket{
ID: id,
Name: fmt.Sprintf("bucket-%v", id),
}, nil
},
}
}
svc, err := dbrp.NewService(context.Background(), f.BucketSvc, s)
if err != nil {
t.Fatal(err)
}
if err := f.Populate(context.Background(), svc); err != nil {
t.Fatal(err)
}
return svc, func() {
if err := itesting.CleanupDBRPMappingsV2(context.Background(), svc); err != nil {
t.Error(err)
}
closeStore()
}
}
func TestBoltDBRPMappingServiceV2(t *testing.T) {
t.Parallel()
itesting.DBRPMappingServiceV2(initDBRPMappingService, t)
}

View File

@ -7,6 +7,145 @@ import (
"unicode"
)
// DBRPMappingServiceV2 provides CRUD to DBRPMappingV2s.
type DBRPMappingServiceV2 interface {
// FindBy returns the dbrp mapping for the specified ID.
// Requires orgID because every resource will be org-scoped.
FindByID(ctx context.Context, orgID, id ID) (*DBRPMappingV2, error)
// FindMany returns a list of dbrp mappings that match filter and the total count of matching dbrp mappings.
FindMany(ctx context.Context, dbrp DBRPMappingFilterV2, opts ...FindOptions) ([]*DBRPMappingV2, int, error)
// Create creates a new dbrp mapping, if a different mapping exists an error is returned.
Create(ctx context.Context, dbrp *DBRPMappingV2) error
// Update a new dbrp mapping
Update(ctx context.Context, dbrp *DBRPMappingV2) error
// Delete removes a dbrp mapping.
// Deleting a mapping that does not exists is not an error.
// Requires orgID because every resource will be org-scoped.
Delete(ctx context.Context, orgID, id ID) error
}
// DBRPMappingV2 represents a mapping of a database and retention policy to an organization ID and bucket ID.
type DBRPMappingV2 struct {
ID ID `json:"id"`
Database string `json:"database"`
RetentionPolicy string `json:"retention_policy"`
// Default indicates if this mapping is the default for the cluster and database.
Default bool `json:"default"`
OrganizationID ID `json:"organization_id"`
BucketID ID `json:"bucket_id"`
}
// Validate reports any validation errors for the mapping.
func (m DBRPMappingV2) Validate() error {
if !validName(m.Database) {
return &Error{
Code: EInvalid,
Msg: "database must contain at least one character and only be letters, numbers, '_', '-', and '.'",
}
}
if !validName(m.RetentionPolicy) {
return &Error{
Code: EInvalid,
Msg: "retentionPolicy must contain at least one character and only be letters, numbers, '_', '-', and '.'",
}
}
if !m.OrganizationID.Valid() {
return &Error{
Code: EInvalid,
Msg: "organizationID is required",
}
}
if !m.BucketID.Valid() {
return &Error{
Code: EInvalid,
Msg: "bucketID is required",
}
}
return nil
}
// Equal checks if the two mappings are identical.
func (m *DBRPMappingV2) Equal(o *DBRPMappingV2) bool {
if m == o {
return true
}
if m == nil || o == nil {
return false
}
return m.Database == o.Database &&
m.RetentionPolicy == o.RetentionPolicy &&
m.Default == o.Default &&
m.OrganizationID.Valid() &&
o.OrganizationID.Valid() &&
m.BucketID.Valid() &&
o.BucketID.Valid() &&
o.ID.Valid() &&
m.ID == o.ID &&
m.OrganizationID == o.OrganizationID &&
m.BucketID == o.BucketID
}
// DBRPMappingFilterV2 represents a set of filters that restrict the returned results.
type DBRPMappingFilterV2 struct {
ID *ID
OrgID *ID
BucketID *ID
Database *string
RetentionPolicy *string
Default *bool
}
func (f DBRPMappingFilterV2) String() string {
var s strings.Builder
s.WriteString("{ id:")
if f.ID != nil {
s.WriteString(f.ID.String())
} else {
s.WriteString("<nil>")
}
s.WriteString(" org_id:")
if f.ID != nil {
s.WriteString(f.OrgID.String())
} else {
s.WriteString("<nil>")
}
s.WriteString(" bucket_id:")
if f.ID != nil {
s.WriteString(f.OrgID.String())
} else {
s.WriteString("<nil>")
}
s.WriteString(" db:")
if f.Database != nil {
s.WriteString(*f.Database)
} else {
s.WriteString("<nil>")
}
s.WriteString(" rp:")
if f.RetentionPolicy != nil {
s.WriteString(*f.RetentionPolicy)
} else {
s.WriteString("<nil>")
}
s.WriteString(" default:")
if f.Default != nil {
s.WriteString(strconv.FormatBool(*f.Default))
} else {
s.WriteString("<nil>")
}
s.WriteString("}")
return s.String()
}
// DBRPMappingService provides a mapping of cluster, database and retention policy to an organization ID and bucket ID.
type DBRPMappingService interface {
// FindBy returns the dbrp mapping the for cluster, db and rp.

12
e2e/.babelrc Normal file
View File

@ -0,0 +1,12 @@
{
"presets": [
[
"env",
{
"targets": {
"node": "current"
}
}
]
]
}

46
e2e/.circleci/config.yml Normal file
View File

@ -0,0 +1,46 @@
version: 2
jobs:
selenium_accept:
docker:
- image: circleci/node:lts-stretch-browsers
- image: quay.io/influxdb/influx:nightly
command: [--e2e-testing=true]
steps:
- checkout
- run:
name: Environment check
command: |
free -h
df -h
git --version
node --version && npm --version
docker --version
google-chrome --version && which google-chrome && chromedriver --version && which chromedriver
timeout 300 bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' localhost:9999)" != "200" ]]; do sleep 5; done' || false
- run:
name: Selenium tests
command: |
set +e
npm install
./node_modules/chromedriver/bin/chromedriver --version
npm test; TEST_RESULT=$?
npm run report:html
npm run report:junit
mkdir -p ~/test-results/cucumber
mkdir -p ~/artifacts/html
cp ~/project/report/cucumber_report.html ~/artifacts/html/cucumber_report.html
cp ~/project/report/cucumber_junit.xml ~/test-results/cucumber/report.xml
cp ~/project/report/cucumber_junit.xml ~/artifacts/report.xml
cp -r ~/project/screenshots ~/artifacts
ls -al
exit $TEST_RESULT
- store_test_results:
path: ~/test-results
- store_artifacts:
path: ~/artifacts
workflows:
version: 2
troubleshoot:
jobs:
- selenium_accept

39
e2e/.eslintrc.json Normal file
View File

@ -0,0 +1,39 @@
{
"env": {
"es6": true,
"node": true
},
"extends": "eslint:recommended",
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly",
"__srcdir" : "readonly",
"__basedir" : "readonly",
"__wdriver" : "readonly",
"__defaultUser": "readonly",
"__config": "readonly",
"__users": "writable"
},
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module"
},
"rules": {
"indent": [
"error",
4
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"always"
]
}
}

27
e2e/Makefile Normal file
View File

@ -0,0 +1,27 @@
.PHONY: docker-build docker-prep docker-test docker-test-kill docker-report test clean
RUNCMD ?= cucumber-js --tags 'not @tested and not @error-collateral'
docker-build:
docker build -t e2e-tests -f scripts/Dockerfile.e2e .
docker-prep:
mkdir -p /tmp/report \
&& docker pull quay.io/influxdb/influx:nightly
docker run -d --rm --name=test-influxdb quay.io/influxdb/influx:nightly influxd --e2e-testing=true \
&& sleep 30s
docker-test: docker-build
docker run --rm --name=test-e2e -v /tmp/report:/selenium-accept-infl2/report --network=container:test-influxdb e2e-tests ${RUNCMD}
docker-report:
docker run --rm -t --name=test-e2e -v /tmp/report:/selenium-accept-infl2/report e2e-tests npm run report:html
docker-test-kill:
docker rm -f test-e2e
test: docker-test docker-report
clean:
docker rm -f test-influxdb
rm -rf /tmp/report

103
e2e/README.md Normal file
View File

@ -0,0 +1,103 @@
## Selenium-Accept
Selenium Acceptance tests for the Influxdbv2 UI.
**Run cycle**
```bash
npm install
npm run influx:setup
npm test
node src/utils/htmlReport.js
node src/utils/junitReport.js
```
Note that the final two reporting steps can be bundled into `package.json` scripts, or can be called as a part of the `package.json` *test* script. They are shown here to show how third party components are used to generate reports.
### Tips
Run only the feature under development
```bash
npm test -- features/onboarding/onboarding.feature:4
```
Number is line number where the target scenario starts.
### API Notes
Steps classes should directly or indirectly inherit from the `baseSteps` class in `baseSteps.js`. This class contains some generic methods to streamline Selenium and Cucumber interactions. More will be added as the test suite grows. Here is a list as of 1.10.2019.
`assertNotPresent(selector)`
`assertNotVisible(element)`
`assertVisible(element)`
`clearInputText(input)`
`clickAndWait(element,
wait = async () => { await this.driver.sleep((await this.driver.manage().getTimeouts()).implicit/20); })`
`clickPopupWizardContinue()`
`clickPopupWizardPrevious()`
`clickPopupWizardFinish()`
`clickPopupCancelBtn()`
`closeAllNotifications()`
`containsNotificationText(text)`
`containsErrorNotification(text)`
`delay(timeout)`
`dismissPopup()`
`hoverOver(element)`
`typeTextAndWait(input, text,
wait = async () => { await this.driver.sleep((await this.driver.manage().getTimeouts()).implicit/20); })`
`verifyElementContainsText(element, text)`
`verifyElementContainsClass(element, clazz)`
`verifyElementDoesNotContainClass(element, clazz)`
`verifyElementDisabled(element)`
`verifyElementErrorMessage(msg, equal = true)`
`verifyElementText(element, text)`
`verifyInputErrorIcon()`
`verifyInputEqualsValue(input, value)`
`verifyInputContainsValue(input, value)`
`verifyInputDoesNotContainValue(input, value)`
`verifyNoElementErrorMessage()`
`verifyNoFormInputErrorIcon()`
`verifyPopupAlertContainsText(text)`
`verifyPopupAlertMatchesRegex(regex)`
`verifyPopupNotPresent()`
`verifyWizardContinueButtonDisabled()`
`verifyWizardDocsLinkURL(url)`
Pages should inherit directly or indirectly from the `basePage` class in `basePage.js`. This contains many common selectors and element getters found either in the over all framework or in common components such as wizards and popups.
The file `commonStepDefs.js` contains a library of test statements leveraging `baseSteps` and covering functionality that will often need to be repeated. It also contains steps for accessing methods from `influxUtils.js` for accessing the REST api through AXIOS, which is useful for setting up test data for a feature set.

103
e2e/cucumber.js Normal file
View File

@ -0,0 +1,103 @@
const chrome = require('selenium-webdriver/chrome');
const ffox = require('selenium-webdriver/firefox');
const fs = require('fs');
const {Builder, Capabilities, By, Key, logging, PageLoadStrategy, promise, until} = require('selenium-webdriver');
//following provides cleaner paths in require statements
global.__basedir = __dirname;
global.__srcdir = __dirname + "/src";
global.__runtime = new Date();
global.__runtimeStr = __runtime.getFullYear().toString() +
(__runtime.getMonth() + 1).toString().padStart(2, '0') +
__runtime.getDate().toString().padStart(2, '0') + "-" +
__runtime.getHours().toString().padStart(2, '0') +
__runtime.getMinutes().toString().padStart(2, '0') +
__runtime.getSeconds().toString().padStart(2, '0');
const { flush, config, defaultUser } = require(__srcdir + '/utils/influxUtils');
global.__screenShotDir = process.cwd() + "/" + __config.screenshot_dir + "/" + __runtimeStr;
global.__dataBuffer = {};
fs.mkdirSync(__screenShotDir, { recursive: true });
var common = '--require "src/step_definitions/**/*.js" --require hooks.js --require-module babel-core/register ';
let caps = new Capabilities();
let chromeUserPreferences = { 'download.prompt_for_download': false, "download.default_directory": __basedir };
let windowSize = { "width": 1024, "height": 768 };
if(__config.window_size){
windowSize.width = parseInt(__config.window_size.width);
windowSize.height = parseInt(__config.window_size.height);
}
console.log("DEBUG windowSize " + JSON.stringify(windowSize));
let logPrefs = new logging.Preferences();
logPrefs.setLevel(logging.Type.BROWSER, logging.Level.ALL);
logPrefs.setLevel(logging.Type.DRIVER, logging.Level.INFO);
let chromeArguments = ['--no-sandbox'];
if(__config.sel_docker){
chromeArguments.push('--disable-dev-shm-usage')
}
if(__config.headless) {
caps.set('applicationCacheEnabled', false);
caps.set('pageLoadStrategy', 'none');
switch (__config.browser.toLowerCase()) {
case "chrome":
global.__wdriver = new Builder()
.withCapabilities(caps)
.forBrowser(__config.browser)
.setChromeOptions(new chrome.Options().headless()
.addArguments(chromeArguments)
.setUserPreferences(chromeUserPreferences)
.setLoggingPrefs(logPrefs)
.windowSize({width: windowSize.width, height: windowSize.height}))
.build();
break;
case "firefox":
global.__wdriver = new Builder()
.forBrowser(__config.browser)
.setFirefoxOptions(new ffox.Options().headless().windowSize({width: windowSize.width,
height: windowSize.height}))
.build();
break;
}
}else{
switch (__config.browser.toLowerCase()) {
case "chrome":
global.__wdriver = new Builder()
.withCapabilities(caps)
.forBrowser(__config.browser)
.setChromeOptions(new chrome.Options().addArguments("--incognito")
.addArguments(chromeArguments)
.setUserPreferences(chromeUserPreferences)
.setLoggingPrefs(logPrefs)
.windowSize({width: windowSize.width, height: windowSize.height}))
.build();
break;
case "firefox":
global.__wdriver = new Builder()
.withCapabilities(caps)
.forBrowser(__config.browser)
.build();
break;
}
}
__wdriver.manage().setTimeouts({implicit: 3000});
__wdriver.executor_.w3c = true;
console.log("DEBUG __wdriver: " + JSON.stringify(__wdriver));
module.exports = {
'default': common + '--format summary --format node_modules/cucumber-pretty --format json:report/cucumber_report.json',
dry: common + '--dry-run',
progress: common + '--format progress'
};

37
e2e/e2e.conf.json Normal file
View File

@ -0,0 +1,37 @@
{
"active": "development",
"default_user": {
"username": "admin",
"password": "changeit",
"org": "qa",
"bucket": "qa"
},
"development" : {
"config_id" : "development",
"protocol": "http",
"host" : "localhost",
"port" : "9999",
"def_ctx": "/",
"headless": false,
"sel_docker": false,
"browser": "chrome",
"screenshot_dir": "screenshots",
"influxdb": {
"version" : "2.0.0"
},
"window_size": {
"width": "1024",
"height": "933"
}
},
"nightly" : {
"config_id": "nightly",
"protocol": "http",
"host": "aws-somewhere",
"port": "9999",
"def_ctx": "/",
"headless": true,
"browser": "chrome",
"screenshot_dir": "screenshots"
}
}

View File

@ -0,0 +1,280 @@
{
"meta": {
"version": "1",
"type": "dashboard",
"name": "Alpha Centauri-Template",
"description": "template created from dashboard: Alpha Centurai"
},
"content": {
"data": {
"type": "dashboard",
"attributes": {
"name": "Alpha Centauri",
"description": ""
},
"relationships": {
"label": {
"data": []
},
"cell": {
"data": [
{
"type": "cell",
"id": "04ce10261819e000"
},
{
"type": "cell",
"id": "04ce1047b959e000"
},
{
"type": "cell",
"id": "04ce106158d9e000"
}
]
},
"variable": {
"data": []
}
}
},
"included": [
{
"id": "04ce10261819e000",
"type": "cell",
"attributes": {
"x": 0,
"y": 0,
"w": 4,
"h": 6
},
"relationships": {
"view": {
"data": {
"type": "view",
"id": "04ce10261819e000"
}
}
}
},
{
"id": "04ce1047b959e000",
"type": "cell",
"attributes": {
"x": 4,
"y": 0,
"w": 8,
"h": 3
},
"relationships": {
"view": {
"data": {
"type": "view",
"id": "04ce1047b959e000"
}
}
}
},
{
"id": "04ce106158d9e000",
"type": "cell",
"attributes": {
"x": 4,
"y": 3,
"w": 8,
"h": 3
},
"relationships": {
"view": {
"data": {
"type": "view",
"id": "04ce106158d9e000"
}
}
}
},
{
"type": "view",
"id": "04ce10261819e000",
"attributes": {
"name": "Hydro",
"properties": {
"shape": "chronograf-v2",
"queries": [
{
"text": "from(bucket: \"qa\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"hydro\")\n |> filter(fn: (r) => r._field == \"level\")",
"editMode": "advanced",
"name": "",
"builderConfig": {
"buckets": [],
"tags": [
{
"key": "_measurement",
"values": []
}
],
"functions": [],
"aggregateWindow": {
"period": "auto"
}
}
}
],
"axes": {
"x": {
"bounds": [
"",
""
],
"label": "",
"prefix": "",
"suffix": "",
"base": "10",
"scale": "linear"
},
"y": {
"bounds": [
"",
""
],
"label": "",
"prefix": "",
"suffix": "",
"base": "10",
"scale": "linear"
}
},
"type": "xy",
"legend": {},
"geom": "line",
"colors": [
{
"id": "2114e3a7-f157-4f0f-ad7c-b953d3cb7cc6",
"type": "scale",
"hex": "#31C0F6",
"name": "Nineteen Eighty Four",
"value": 0
},
{
"id": "3fc62935-3abc-47ba-a7ab-d44a19bbcc3f",
"type": "scale",
"hex": "#A500A5",
"name": "Nineteen Eighty Four",
"value": 0
},
{
"id": "a55a66a7-0f89-4608-9550-f1cae7bf3cf0",
"type": "scale",
"hex": "#FF7E27",
"name": "Nineteen Eighty Four",
"value": 0
}
],
"note": "",
"showNoteWhenEmpty": false,
"xColumn": "_time",
"yColumn": "_value",
"shadeBelow": false
}
}
},
{
"type": "view",
"id": "04ce1047b959e000",
"attributes": {
"name": "Sinusoid",
"properties": {
"shape": "chronograf-v2",
"queries": [
{
"text": "from(bucket: \"qa\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"sine\")\n |> filter(fn: (r) => r._field == \"beat\")",
"editMode": "advanced",
"name": "",
"builderConfig": {
"buckets": [],
"tags": [
{
"key": "_measurement",
"values": []
}
],
"functions": [],
"aggregateWindow": {
"period": "auto"
}
}
}
],
"axes": {
"x": {
"bounds": [
"",
""
],
"label": "",
"prefix": "",
"suffix": "",
"base": "10",
"scale": "linear"
},
"y": {
"bounds": [
"",
""
],
"label": "",
"prefix": "",
"suffix": "",
"base": "10",
"scale": "linear"
}
},
"type": "xy",
"legend": {},
"geom": "line",
"colors": [
{
"id": "2114e3a7-f157-4f0f-ad7c-b953d3cb7cc6",
"type": "scale",
"hex": "#31C0F6",
"name": "Nineteen Eighty Four",
"value": 0
},
{
"id": "3fc62935-3abc-47ba-a7ab-d44a19bbcc3f",
"type": "scale",
"hex": "#A500A5",
"name": "Nineteen Eighty Four",
"value": 0
},
{
"id": "a55a66a7-0f89-4608-9550-f1cae7bf3cf0",
"type": "scale",
"hex": "#FF7E27",
"name": "Nineteen Eighty Four",
"value": 0
}
],
"note": "",
"showNoteWhenEmpty": false,
"xColumn": "_time",
"yColumn": "_value",
"shadeBelow": false
}
}
},
{
"type": "view",
"id": "04ce106158d9e000",
"attributes": {
"name": "Name this Cell",
"properties": {
"shape": "chronograf-v2",
"type": "markdown",
"note": "This is just a test dashboard to be used for import."
}
}
}
]
},
"labels": []
}

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1,175 @@
{
"meta": {
"name": "Hydro test dashboard-Template",
"type": "dashboard",
"description": "template created from dashboard: Hydro test dashboard",
"version": "1",
"createdAt": "2019-10-21T12:46:22.452466985Z",
"updatedAt": "2019-10-21T12:46:22.452467111Z"
},
"content": {
"data": {
"attributes": {
"description": "",
"name": "Hydro test dashboard"
},
"relationships": {
"cell": {
"data": [
{
"id": "04a79573194a5000",
"type": "cell"
},
{
"id": "04a795c71d4a5000",
"type": "cell"
}
]
},
"label": {
"data": []
},
"variable": {
"data": []
}
},
"type": "dashboard"
},
"included": [
{
"attributes": {
"h": 4,
"w": 4,
"x": 0,
"y": 0
},
"id": "04a79573194a5000",
"relationships": {
"view": {
"data": {
"id": "04a79573194a5000",
"type": "view"
}
}
},
"type": "cell"
},
{
"attributes": {
"h": 4,
"w": 4,
"x": 4,
"y": 0
},
"id": "04a795c71d4a5000",
"relationships": {
"view": {
"data": {
"id": "04a795c71d4a5000",
"type": "view"
}
}
},
"type": "cell"
},
{
"attributes": {
"name": "Name this Cell",
"properties": {
"axes": {
"x": {
"base": "10",
"bounds": [
"",
""
],
"label": "",
"prefix": "",
"scale": "linear",
"suffix": ""
},
"y": {
"base": "10",
"bounds": [
"",
""
],
"label": "",
"prefix": "",
"scale": "linear",
"suffix": ""
}
},
"colors": [
{
"hex": "#31C0F6",
"id": "f583e27b-5ada-4eb2-9ac6-37af00f3c016",
"name": "Nineteen Eighty Four",
"type": "scale",
"value": 0
},
{
"hex": "#A500A5",
"id": "26bb82bd-364a-4ff6-b0ce-5905adc15bf2",
"name": "Nineteen Eighty Four",
"type": "scale",
"value": 0
},
{
"hex": "#FF7E27",
"id": "932f9e8b-1aee-49b7-841a-238bb2a73ea9",
"name": "Nineteen Eighty Four",
"type": "scale",
"value": 0
}
],
"geom": "line",
"legend": {},
"note": "",
"queries": [
{
"builderConfig": {
"aggregateWindow": {
"period": "auto"
},
"buckets": [],
"functions": [],
"tags": [
{
"key": "_measurement",
"values": []
}
]
},
"editMode": "advanced",
"name": "",
"text": "from(bucket: \"qa\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"hydro\")\n |> filter(fn: (r) => r._field == \"level\")"
}
],
"shadeBelow": false,
"shape": "chronograf-v2",
"showNoteWhenEmpty": false,
"type": "xy",
"xColumn": "_time",
"yColumn": "_value"
}
},
"id": "04a79573194a5000",
"type": "view"
},
{
"attributes": {
"name": "Name this Cell",
"properties": {
"note": "This is dashboard shows randomized hydrological data for a month time window. To see the curve set the time window to at least 2d. ",
"shape": "chronograf-v2",
"type": "markdown"
}
},
"id": "04a795c71d4a5000",
"type": "view"
}
]
},
"labels": []
}

View File

@ -0,0 +1 @@
bad data

View File

@ -0,0 +1,68 @@
{
"meta": {
"name": "Note Dashboard-Template",
"type": "dashboard",
"description": "template created from dashboard: Note Dashboard",
"version": "1",
"createdAt": "2019-10-21T14:16:31.163226513Z",
"updatedAt": "2019-10-21T14:16:31.163226637Z"
},
"content": {
"data": {
"attributes": {
"description": "",
"name": "Note Dashboard"
},
"relationships": {
"cell": {
"data": [
{
"id": "04a7aa8e284a5000",
"type": "cell"
}
]
},
"label": {
"data": []
},
"variable": {
"data": []
}
},
"type": "dashboard"
},
"included": [
{
"attributes": {
"h": 4,
"w": 4,
"x": 0,
"y": 0
},
"id": "04a7aa8e284a5000",
"relationships": {
"view": {
"data": {
"id": "04a7aa8e284a5000",
"type": "view"
}
}
},
"type": "cell"
},
{
"attributes": {
"name": "Name this Cell",
"properties": {
"note": "This dashboard just contains a note",
"shape": "chronograf-v2",
"type": "markdown"
}
},
"id": "04a7aa8e284a5000",
"type": "view"
}
]
},
"labels": []
}

View File

@ -0,0 +1,100 @@
{
"meta": {
"version": "1",
"type": "dashboard",
"name": "Notepad-Template",
"description": "template created from dashboard: Notepad"
},
"content": {
"data": {
"type": "dashboard",
"attributes": {
"name": "Notepad",
"description": ""
},
"relationships": {
"label": {
"data": []
},
"cell": {
"data": [
{
"type": "cell",
"id": "04a8ab4020f81000"
},
{
"type": "cell",
"id": "04a8ab5327b81000"
}
]
},
"variable": {
"data": []
}
}
},
"included": [
{
"id": "04a8ab4020f81000",
"type": "cell",
"attributes": {
"x": 0,
"y": 0,
"w": 4,
"h": 4
},
"relationships": {
"view": {
"data": {
"type": "view",
"id": "04a8ab4020f81000"
}
}
}
},
{
"id": "04a8ab5327b81000",
"type": "cell",
"attributes": {
"x": 4,
"y": 0,
"w": 4,
"h": 4
},
"relationships": {
"view": {
"data": {
"type": "view",
"id": "04a8ab5327b81000"
}
}
}
},
{
"type": "view",
"id": "04a8ab4020f81000",
"attributes": {
"name": "Name this Cell",
"properties": {
"shape": "chronograf-v2",
"type": "markdown",
"note": "Simple note 1"
}
}
},
{
"type": "view",
"id": "04a8ab5327b81000",
"attributes": {
"name": "Name this Cell",
"properties": {
"shape": "chronograf-v2",
"type": "markdown",
"note": "Simple note 2"
}
}
}
]
},
"labels": []
}

View File

@ -0,0 +1,173 @@
{
"meta": {
"version": "1",
"type": "dashboard",
"name": "Sinusoid test data-Template",
"description": "template created from dashboard: Sinusoid test data"
},
"content": {
"data": {
"type": "dashboard",
"attributes": {
"name": "Sinusoid test data",
"description": ""
},
"relationships": {
"label": {
"data": []
},
"cell": {
"data": [
{
"type": "cell",
"id": "04a8a9fb2f381000"
},
{
"type": "cell",
"id": "04a8aa39b4b81000"
}
]
},
"variable": {
"data": []
}
}
},
"included": [
{
"id": "04a8a9fb2f381000",
"type": "cell",
"attributes": {
"x": 0,
"y": 0,
"w": 4,
"h": 4
},
"relationships": {
"view": {
"data": {
"type": "view",
"id": "04a8a9fb2f381000"
}
}
}
},
{
"id": "04a8aa39b4b81000",
"type": "cell",
"attributes": {
"x": 4,
"y": 0,
"w": 4,
"h": 4
},
"relationships": {
"view": {
"data": {
"type": "view",
"id": "04a8aa39b4b81000"
}
}
}
},
{
"type": "view",
"id": "04a8a9fb2f381000",
"attributes": {
"name": "Beat goes on",
"properties": {
"shape": "chronograf-v2",
"queries": [
{
"text": "from(bucket: \"qa\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"sine\")\n |> filter(fn: (r) => r._field == \"beat\")",
"editMode": "advanced",
"name": "",
"builderConfig": {
"buckets": [],
"tags": [
{
"key": "_measurement",
"values": []
}
],
"functions": [],
"aggregateWindow": {
"period": "auto"
}
}
}
],
"axes": {
"x": {
"bounds": [
"",
""
],
"label": "",
"prefix": "",
"suffix": "",
"base": "10",
"scale": "linear"
},
"y": {
"bounds": [
"",
""
],
"label": "",
"prefix": "",
"suffix": "",
"base": "10",
"scale": "linear"
}
},
"type": "xy",
"legend": {},
"geom": "line",
"colors": [
{
"id": "07cc0b96-91db-4751-9548-e99502e7e825",
"type": "scale",
"hex": "#31C0F6",
"name": "Nineteen Eighty Four",
"value": 0
},
{
"id": "81273781-dae1-4432-b8cd-7f5e235d632f",
"type": "scale",
"hex": "#A500A5",
"name": "Nineteen Eighty Four",
"value": 0
},
{
"id": "b8f3e7e2-4e5c-4e43-9604-7ae2f7295876",
"type": "scale",
"hex": "#FF7E27",
"name": "Nineteen Eighty Four",
"value": 0
}
],
"note": "",
"showNoteWhenEmpty": false,
"xColumn": "_time",
"yColumn": "_value",
"shadeBelow": false
}
}
},
{
"type": "view",
"id": "04a8aa39b4b81000",
"attributes": {
"name": "Name this Cell",
"properties": {
"shape": "chronograf-v2",
"type": "markdown",
"note": "Data generated over a 1 month time frame, so use at least 2d window to get a proper view. "
}
}
}
]
},
"labels": []
}

View File

@ -0,0 +1,280 @@
{
"meta": {
"version": "1",
"type": "dashboard",
"name": "Tau Ceti-Template",
"description": "template created from dashboard: Tau Ceti"
},
"content": {
"data": {
"type": "dashboard",
"attributes": {
"name": "Tau Ceti",
"description": ""
},
"relationships": {
"label": {
"data": []
},
"cell": {
"data": [
{
"type": "cell",
"id": "04ce423aabd9e000"
},
{
"type": "cell",
"id": "04ce4313b159e000"
},
{
"type": "cell",
"id": "04ce43c11ad9e000"
}
]
},
"variable": {
"data": []
}
}
},
"included": [
{
"id": "04ce423aabd9e000",
"type": "cell",
"attributes": {
"x": 0,
"y": 0,
"w": 12,
"h": 4
},
"relationships": {
"view": {
"data": {
"type": "view",
"id": "04ce423aabd9e000"
}
}
}
},
{
"id": "04ce4313b159e000",
"type": "cell",
"attributes": {
"x": 0,
"y": 4,
"w": 7,
"h": 2
},
"relationships": {
"view": {
"data": {
"type": "view",
"id": "04ce4313b159e000"
}
}
}
},
{
"id": "04ce43c11ad9e000",
"type": "cell",
"attributes": {
"x": 7,
"y": 4,
"w": 5,
"h": 2
},
"relationships": {
"view": {
"data": {
"type": "view",
"id": "04ce43c11ad9e000"
}
}
}
},
{
"type": "view",
"id": "04ce423aabd9e000",
"attributes": {
"name": "Hydro derivative",
"properties": {
"shape": "chronograf-v2",
"queries": [
{
"text": "from(bucket: \"qa\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"hydro\")\n |> filter(fn: (r) => r._field == \"level\")\n |> derivative(unit: v.windowPeriod, nonNegative: false)\n |> yield(name: \"derivative\")",
"editMode": "advanced",
"name": "",
"builderConfig": {
"buckets": [],
"tags": [
{
"key": "_measurement",
"values": []
}
],
"functions": [],
"aggregateWindow": {
"period": "auto"
}
}
}
],
"axes": {
"x": {
"bounds": [
"",
""
],
"label": "",
"prefix": "",
"suffix": "",
"base": "10",
"scale": "linear"
},
"y": {
"bounds": [
"",
""
],
"label": "",
"prefix": "",
"suffix": "",
"base": "10",
"scale": "linear"
}
},
"type": "xy",
"legend": {},
"geom": "line",
"colors": [
{
"id": "6b3be0c4-ed87-4df3-b849-94ab15e9a254",
"type": "scale",
"hex": "#31C0F6",
"name": "Nineteen Eighty Four",
"value": 0
},
{
"id": "1b30d357-e796-493b-a423-38aa3c850980",
"type": "scale",
"hex": "#A500A5",
"name": "Nineteen Eighty Four",
"value": 0
},
{
"id": "871f9ba2-5042-4643-a0a0-355b76f5990b",
"type": "scale",
"hex": "#FF7E27",
"name": "Nineteen Eighty Four",
"value": 0
}
],
"note": "",
"showNoteWhenEmpty": false,
"xColumn": "_time",
"yColumn": "_value",
"shadeBelow": false
}
}
},
{
"type": "view",
"id": "04ce4313b159e000",
"attributes": {
"name": "Sinusoid sum - missed points",
"properties": {
"shape": "chronograf-v2",
"queries": [
{
"text": "from(bucket: \"qa\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"sine\")\n |> filter(fn: (r) => r._field == \"beat\")\n |> aggregateWindow(every: 1h, fn: sum)\n |> yield(name: \"sum\")",
"editMode": "advanced",
"name": "",
"builderConfig": {
"buckets": [],
"tags": [
{
"key": "_measurement",
"values": []
}
],
"functions": [],
"aggregateWindow": {
"period": "auto"
}
}
}
],
"axes": {
"x": {
"bounds": [
"",
""
],
"label": "",
"prefix": "",
"suffix": "",
"base": "10",
"scale": "linear"
},
"y": {
"bounds": [
"",
""
],
"label": "",
"prefix": "",
"suffix": "",
"base": "10",
"scale": "linear"
}
},
"type": "xy",
"legend": {},
"geom": "line",
"colors": [
{
"id": "6b3be0c4-ed87-4df3-b849-94ab15e9a254",
"type": "scale",
"hex": "#31C0F6",
"name": "Nineteen Eighty Four",
"value": 0
},
{
"id": "1b30d357-e796-493b-a423-38aa3c850980",
"type": "scale",
"hex": "#A500A5",
"name": "Nineteen Eighty Four",
"value": 0
},
{
"id": "871f9ba2-5042-4643-a0a0-355b76f5990b",
"type": "scale",
"hex": "#FF7E27",
"name": "Nineteen Eighty Four",
"value": 0
}
],
"note": "",
"showNoteWhenEmpty": false,
"xColumn": "_time",
"yColumn": "_value",
"shadeBelow": false
}
}
},
{
"type": "view",
"id": "04ce43c11ad9e000",
"attributes": {
"name": "Name this Cell",
"properties": {
"shape": "chronograf-v2",
"type": "markdown",
"note": "This is just a test dashboard. Ceci n'est qu'un tableau d'essai. Tohle jsou jen zkusebni deska. "
}
}
}
]
},
"labels": []
}

View File

@ -0,0 +1,30 @@
{
"id":null,
"type":"deadman",
"status":"active",
"activeStatus":"active",
"name":"Deadman Crit Check",
"query":
{
"name":"",
"text":"from(bucket: \"qa\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"test\")\n |> filter(fn: (r) => r[\"_field\"] == \"val\")",
"editMode":"builder",
"builderConfig":
{
"buckets":["qa"],
"tags":[{"key":"_measurement","values":["test"],"aggregateFunctionType":"filter"},{"key":"_field","values":["val"],"aggregateFunctionType":"filter"},{"key":"gen","values":[],"aggregateFunctionType":"filter"}],
"functions":[]
},
"hidden":false
},
"orgID":"05a7c4e449675000",
"labels":[],
"every":"5s",
"level":"CRIT",
"offset":"2s",
"reportZero":false,
"staleTime":"1m",
"statusMessageTemplate":"Check: ${ r._check_name } is: ${ r._level }",
"tags":[],
"timeSince":"30s"
}

View File

@ -0,0 +1,36 @@
{
"id":null,
"type":"threshold",
"status":"active",
"activeStatus":"active",
"name":"Threshold Check from File",
"query":{
"name":"",
"text":"from(bucket: \"qa\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"test\")\n |> filter(fn: (r) => r[\"_field\"] == \"val\")\n |> aggregateWindow(every: 5s, fn: mean)\n |> yield(name: \"mean\")",
"editMode":"builder",
"builderConfig": {
"buckets":["qa"],
"tags":[
{"key":"_measurement","values":["test"],"aggregateFunctionType":"filter"},
{"key":"_field","values":["val"],"aggregateFunctionType":"filter"},
{"key":"gen","values":[],"aggregateFunctionType":"filter"}
],
"functions":[
{"name":"mean"}
],
"aggregateWindow":{"period":"5s"}
},
"hidden":false
},
"orgID":"05a67e89563c3000",
"labels":[],
"every":"5s",
"offset":"1s",
"statusMessageTemplate":"Check: ${ r._check_name } is: ${ r._level }",
"tags":[],
"thresholds":[
{"type":"greater","value":7.5,"level":"CRIT"},
{"type":"lesser","value":1.5,"level":"WARN"},
{"type":"range","level":"INFO","min":4.5,"max":5.5,"within":true}
]
}

View File

@ -0,0 +1,44 @@
{
"meta": {
"version": "1",
"type": "variable",
"name": "Arsenal-Template",
"description": "template created from variable: Arsenal"
},
"content": {
"data": {
"type": "variable",
"attributes": {
"name": "Arsenal",
"arguments": {
"type": "constant",
"values": [
"Henry",
"Wright",
"Bastin",
"Radford",
"Brain",
"Drake",
"Lishman",
"van Persie",
"Hulme",
"Jack"
]
},
"selected": [
"Henry"
]
},
"relationships": {
"variable": {
"data": []
},
"label": {
"data": []
}
}
},
"included": []
},
"labels": []
}

View File

@ -0,0 +1,44 @@
{
"meta": {
"version": "1",
"type": "variable",
"name": "Slavia-Template",
"description": "template created from variable: Slavia"
},
"content": {
"data": {
"type": "variable",
"attributes": {
"name": "Slavia",
"arguments": {
"type": "constant",
"values": [
"cestina",
"polstina",
"slovenstina",
"serbstina",
"chorvatstina",
"bosenstina",
"bulharstina",
"ukranstina",
"belorustina",
"rustina"
]
},
"selected": [
"cestina"
]
},
"relationships": {
"variable": {
"data": []
},
"label": {
"data": []
}
}
},
"included": []
},
"labels": []
}

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1,39 @@
{
"meta": {
"version": "1",
"type": "variable",
"name": "Jehlicnany-Template",
"description": "template created from variable: Jehlicnany"
},
"content": {
"data": {
"type": "variable",
"attributes": {
"name": "Jehlicnany",
"arguments": {
"type": "map",
"values": {
"borovice": "pinus",
"jedle": "abies",
"modrin": "larix",
"tis": "taxus",
"zerav": "tuje"
}
},
"selected": [
"jedle"
]
},
"relationships": {
"variable": {
"data": []
},
"label": {
"data": []
}
}
},
"included": []
},
"labels": []
}

View File

@ -0,0 +1,37 @@
{
"meta": {
"version": "1",
"type": "variable",
"name": "Ryby-Template",
"description": "template created from variable: Ryby"
},
"content": {
"data": {
"type": "variable",
"attributes": {
"name": "Ryby",
"arguments": {
"type": "map",
"values": {
"kapr": "Cyprinus carpio",
"lin": "Tinca tinca",
"losos": "Salmo salar",
"pstruh": "Salmo trutta",
"sih": "Coregonus"
}
},
"selected": null
},
"relationships": {
"variable": {
"data": []
},
"label": {
"data": []
}
}
},
"included": []
},
"labels": []
}

View File

@ -0,0 +1,34 @@
{
"meta": {
"version": "1",
"type": "variable",
"name": "bucket-Template",
"description": "template for variable - bucket"
},
"content": {
"data": {
"type": "variable",
"attributes": {
"name": "Bucket",
"arguments": {
"type": "query",
"values": {
"query": "buckets()\n |> filter(fn: (r) => r.name !~ /^_/)\n |> rename(columns: {name: \"_value\"})\n |> keep(columns: [\"_value\"])\n",
"language": "flux"
}
},
"selected": null
},
"relationships": {
"variable": {
"data": []
},
"label": {
"data": []
}
}
},
"included": []
},
"labels": []
}

View File

@ -0,0 +1,677 @@
@feature-dashboards
@dashboards-cellEdit
Feature: Dashboards - Dashboard - Cell Edit
As a user I want to Create and Update Cells
So that I can view specific Influxdbv2 data
@tested
Scenario: Load Cell Edit View
Given I reset the environment
Given run setup over REST "DEFAULT"
When open the signin page
When UI sign in user "DEFAULT"
When click nav menu item "Dashboards"
# When hover over the "Dashboards" menu item
# When click nav sub menu "Dashboards"
Then the Dashboards page is loaded
When API sign in user "DEFAULT"
When generate a line protocol testdata for user "DEFAULT" based on:
"""
{ "points": 2880, "measurement":"pulse", "start": "-48h", "algo": "log", "prec": "sec", "name": "beat"}
"""
When generate a line protocol testdata for user "DEFAULT" based on:
"""
{ "points": 7200, "measurement":"signal", "start": "-30d", "algo": "sine", "prec": "sec", "name": "foo"}
"""
When click the empty Create dashboard dropdown button
When click the create dashboard item "New Dashboard"
Then the new dashboard page is loaded
Then the empty dashboard contains a documentation link
Then the empty dashboard contains Add a Cell button
When name dashboard "Fitness"
# +page-title - edit name this cell # covered in dashboard test
# +Graph dropdown
# +cog-cell--button
# +cancel-cell-edit--button
# +save-cell--button # already covered in dashboard test
# time-machine--view
# +empty-graph--no-queries
# +giraffe-autosizer
# +raw-data-table
# time-machine--bottom
# +raw-data--toggle
# +CSV download
# +Refresh Rate
# +Reload
# +Time Range
# +switch-to-script-editor
# +Submit#
# +Add Query
# +Queries
# +Builder
# +Schema navigator
# +Aggregate functions
# +Script Editor
# +Aggregate functions
# +Variables
@tested
Scenario: Exercise Basic Cell Edit Controls
When click the empty create cell button
Then the cell edit overlay is loaded as "Name this Cell"
When name dashboard cell "Kliky"
When click dashboard cell edit cancel button
Then there is no dashboard cell named "Kliky"
When click the empty create cell button
Then the cell edit overlay is loaded as "Name this Cell"
When name dashboard cell "Kliky"
When click the dashboard cell view type dropdown
Then the dashboard cell view type dropdown list contains:
"""
xy,line-plus-single-stat,heatmap,histogram,single-stat,gauge,table,scatter
"""
When click the dashboard cell view type dropdown
Then the cell view type dropdown list is not present
When click cell view customize button
Then the view options container is present
Then the cell view customize button is highlighted
When click cell view customize button
Then the view options container is not present
Then the cell view customize button is not highlighted
Then the time machine view empty queries graph is visible
When click time machine autorefresh dropdown
Then the time machine autorefresh dropdown list contains:
"""
Paused,5s,10s,30s,60s
"""
When select the time machine autorefresh rate "60s"
Then the time machine force refresh button is not present
When click time machine autorefresh dropdown
When select the time machine autorefresh rate "Paused"
Then the time machine force refresh button is present
When click the cell edit Time Range Dropdown
Then the time machine Time Range dropdown list contains:
"""
Custom Time Range,Past 5m,Past 15m,Past 1h,Past 6h,Past 12h,Past 24h,Past 2d,Past 7d,Past 30d
"""
When click the cell edit Time Range Dropdown
Then the time machine Time Range dropdown list is not present
Then the time machine query builder is visible
When click the cell edit Script Editor button
Then the time machine flux editor is visible
When click the cell edit Query Builder button
When click the time machine flux editor
Then the time machine flux editor is visible
Then the time machine switch to Query Builder warning is not present
When click the cell edit Query Builder button
When click the cell edit Query Builder confirm button
Then the time machine query builder is visible
Then the time machine switch to Query Builder warning is not present
Then the time machine flux editor is not present
When click dashboard cell save button
Then the dashboard contains a cell named "Kliky"
# ~~page-title -- name edit
# ~~Graph drop down
# ~~cog-cell--button
# cancel-cell-edit--button
# save-cell-edit--button
# ~~time-machine--view
# ~~empty-graph--no-queries
# time-machine--bottom
# ~~Refresh Rate -- N.B. pause has update button which disappears with other refresh rate values
# ~~Time Range
# ~~switch-to-script-editor
# ~~switch-to-query-builder
@tested
Scenario: Exercise Query Builder
When toggle context menu of dashboard cell named "Kliky"
When click cell content popover configure
Then the cell edit overlay is loaded as "Kliky"
Then the time machine cell edit submit button is disabled
Then the edit cell bucket selector contains buckets:
"""
qa,_monitoring,_tasks
"""
When filter the time machine bucket selector with "t"
Then the edit cell bucket selector contains buckets:
"""
_monitoring,_tasks
"""
Then the bucket "qa" is not present in the time machine bucket selector
When clear the time machine bucket selector filter
Then the edit cell bucket selector contains buckets:
"""
qa,_monitoring,_tasks
"""
When click the time machine bucket selector item "_monitoring"
Then time machine bulider card "1" contains the empty tag message
When click the time machine bucket selector item "qa"
Then there are "1" time machine builder cards
Then time machine builder card "1" contains:
"""
beat,foo
"""
When click the tag selector dropdown of builder card "1"
Then the tag selector dropdown of builder card "1" contains:
"""
_field,_measurement,test
"""
When click the tag selector dropdown item "_field" of builder card "1"
Then time machine builder card "1" contains:
"""
pulse,signal
"""
When click the tag selector dropdown of builder card "1"
When click the tag selector dropdown item "_measurement" of builder card "1"
Then time machine builder card "1" contains:
"""
beat,foo
"""
When filter the tags in time machine builder card "1" with "eat"
Then time machine builder card "1" does not contain "foo"
When click the tag "beat" in builder card "1"
Then the time machine cell edit submit button is enabled
Then there are "2" time machine builder cards
Then time machine builder card "2" contains:
"""
pulse
"""
When filter the tags in time machine builder card "2" with "ratfink"
Then time machine builder card "2" is empty
When clear the tags filter in time machine builder card "2"
Then time machine builder card "2" contains:
"""
pulse
"""
When clear the tags filter in time machine builder card "1"
Then time machine builder card "1" contains:
"""
beat,foo
"""
When click the tag "foo" in builder card "1"
Then time machine builder card "2" contains:
"""
pulse,signal
"""
Then the selector count for builder card "1" contains the value "2"
When click the tag selector dropdown of builder card "2"
Then the tag selector dropdown of builder card "2" contains:
"""
_field,test
"""
When click the tag selector dropdown of builder card "2"
Then the contents of tag selector dropodwn of build card "2" are not present
When click the tag "foo" in builder card "1"
Then the selector count for builder card "1" contains the value "1"
When click the tag "beat" in builder card "1"
Then the selector counf for builder card "1" is not present
Then the delete button for builder card "1" is not present
When click delete for builder card "2"
Then there are "1" time machine builder cards
# Check coverage of issue 16682 once fixed
@tested
Scenario: Exercise Query Builder Functions
# TODO 16682 - following will not have been reset
# Then the time machine query builder function duration period is "auto"
When click the tag "beat" in builder card "1"
Then the time machine query builder function duration period is "auto (10s)"
Then the query builder function list contains
"""
mean, median, max, min, sum, derivative, nonnegative derivative, distinct, count, increase,
skew, spread, stddev, first, last, unique, sort
"""
When filter the query builder function list with "rx"
Then the query builder function list has "0" items
When clear the query builder function lis filter
Then the query builder function list contains
"""
mean, median, max, min, sum, derivative, nonnegative derivative, distinct, count, increase,
skew, spread, stddev, first, last, unique, sort
"""
When filter the query builder function list with "in"
Then the query builder function list contains
"""
min,distinct,increase
"""
Then the query builder function list has "3" items
When click the time machine query builder function duration input
Then the query builder function duration suggestion drop down contains "14" suggestions
Then the query builder function duration suggestion drop down includes
"""
auto (10s),none,5s,15s,1m,5m,15m,1h,6h,12h,24h,2d,7d,30d
"""
When click the query builder function duration suggestion "7d"
Then the time machine query builder function duration period is "7d"
When click the time machine query builder function duration input
When click the query builder function duration suggestion "auto (10s)"
Then the time machine query builder function duration period is "auto (10s)"
When click dashboard cell edit cancel button
@tested
Scenario: Create basic query
Then the cell named "Kliky" contains the empty graph message
When toggle context menu of dashboard cell named "Kliky"
When click cell content popover configure
Then the time machine view no results is visible
Then the time machine cell edit submit button is disabled
When click the time machine bucket selector item "qa"
When click the tag "beat" in builder card "1"
Then the time machine cell edit submit button is enabled
When click the tag "pulse" in builder card "2"
When click the time machine cell edit submit button
Then the time machine cell edit preview graph is shown
Then the time machine cell edit preview axes are shown
@error-collateral
Scenario: Resize Preview
When get metrics of time machine cell edit preview
When get metrics of time machine query builder
When get time machine preview canvas
When get time machine preview axes
## Resize - larger
When resize time machine preview area by "{ "dw": "0", "dh": "+200" }"
# When wait "3" seconds
# Compare new dims Dashboard steps 387
Then the time machine preview area has changed by "{ "dw": "0", "dh": "+200" }"
Then the time machine query builder area has changed by "{ "dw": "0", "dh": "-200" }"
Then the time machine preview canvas has changed
Then the time machine preview axes have changed
# Git dims again
When get metrics of time machine cell edit preview
When get metrics of time machine query builder
When get time machine preview canvas
When get time machine preview axes
# Resize - smaller
When resize time machine preview area by "{ "dw": "0", "dh": "-300" }"
# Compare new dims
Then the time machine preview area has changed by "{ "dw": "0", "dh": "-300" }"
Then the time machine query builder area has changed by "{ "dw": "0", "dh": "+300" }"
Then the time machine preview canvas has changed
Then the time machine preview axes have changed
When click dashboard cell save button
Then the cell named "Kliky" contains a graph
@error-collateral
Scenario: Create Second Query
When get the current graph of the cell "Kliky"
When toggle context menu of dashboard cell named "Kliky"
When click cell content popover configure
Then the time machine cell edit preview graph is shown
Then the time machine cell edit preview axes are shown
Then the bucket selected in the current time machine query is "qa"
Then the tag selected in the current time machine query card "1" is "beat"
Then the tag selected in the current time machine query card "2" is "pulse"
When get time machine preview canvas
When get time machine preview axes
When click the time machine query builder add query button
Then the bucket selected in the current time machine query is "qa"
Then there are "1" time machine builder cards
Then time machine builder card "1" contains:
"""
beat,foo
"""
Then there are no selected tags in time machine builder card "1"
When click the tag "foo" in builder card "1"
When click the tag "signal" in builder card "2"
When click the query builder function "mean"
When click the time machine query builder function duration input
When click the query builder function duration suggestion "1m"
When click the time machine cell edit submit button
Then the time machine preview canvas has changed
Then the time machine preview axes have changed
When click dashboard cell save button
Then the graph of the cell "Kliky" has changed
@error-collateral
Scenario: Change Query Name
When get the current graph of the cell "Kliky"
When toggle context menu of dashboard cell named "Kliky"
When click cell content popover configure
Then query "Query 1" is the active query in query builder
When click on query "Query 2" in the query builder
Then the bucket selected in the current time machine query is "qa"
Then the tag selected in the current time machine query card "1" is "foo"
Then the tag selected in the current time machine query card "2" is "signal"
Then the functions selected in the current time machine query card are "mean"
When right click on the time machine query tab title "Query 2"
When click the time machine query tab right click menu item "Edit"
When enter "Dotaz B" into the time machine query tab name input
Then there is no time machine query tab named "Query 2"
Then query "Dotaz B" is the active query in query builder
@error-collateral
Scenario: Hide Query
When get time machine preview canvas
When get time machine preview axes
When click hide query of time machine query tab "Dotaz B"
Then the time machine preview canvas has changed
Then the time machine preview axes have changed
When get time machine preview canvas
When get time machine preview axes
When click hide query of time machine query tab "Dotaz B"
Then the time machine preview canvas has changed
Then the time machine preview axes have changed
When click dashboard cell save button
@tested
Scenario: Delete Second Query
When toggle context menu of dashboard cell named "Kliky"
When click cell content popover configure
When get the current graph of the cell "Kliky"
When right click the time machine query tab "Dotaz B"
When get time machine preview canvas
When get time machine preview axes
#When click delete of time machine query tab "Dotaz B"
When click the time machine query tab right click menu item "remove"
Then there is no time machine query tab named "Dotaz B"
Then there are "1" time machine query tabs
Then the time machine preview canvas has changed
Then the time machine preview axes have changed
When click dashboard cell save button
Then the graph of the cell "Kliky" has changed
@tested
Scenario: Edit Query
When get the current graph of the cell "Kliky"
When toggle context menu of dashboard cell named "Kliky"
When click cell content popover configure
When get time machine preview canvas
When get time machine preview axes
When click the cell edit Script Editor button
Then the time machine script editor contains
"""
from(bucket: "qa")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["_measurement"] == "beat")
|> filter(fn: (r) => r["_field"] == "pulse")
"""
When change the time machine script editor contents to:
"""
from(bucket: "qa")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["_measurement"] == "foo")
|> filter(fn: (r) => r["_field"] == "signal")
"""
When click the time machine cell edit submit button
Then the time machine preview canvas has changed
Then the time machine preview axes have changed
When click the cell edit save button
Then the graph of the cell "Kliky" has changed
@tested
Scenario: Switch to Query Builder
When get the current graph of the cell "Kliky"
When toggle context menu of dashboard cell named "Kliky"
When click cell content popover configure
Then the time machine script editor contains
"""
from(bucket: "qa")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["_measurement"] == "foo")
|> filter(fn: (r) => r["_field"] == "signal")
"""
When get time machine preview canvas
When get time machine preview axes
When click the cell edit Query Builder button
Then the time machine switch to Query Builder warning is present
When click the time machine flux editor
Then the time machine switch to Query Builder warning is not present
When click the cell edit Query Builder button
When click the cell edit Query Builder confirm button
Then the time machine query builder is visible
# Issue 16731 todo - check how query is reflected in query builder state
Then the time machine preview canvas has changed
Then the time machine preview axes have changed
When click dashboard cell edit cancel button
Then the graph of the cell "Kliky" has not changed
@tested
Scenario: Edit invalid query
When get the current graph of the cell "Kliky"
When toggle context menu of dashboard cell named "Kliky"
When click cell content popover configure
Then the time machine script editor contains
"""
from(bucket: "qa")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["_measurement"] == "foo")
|> filter(fn: (r) => r["_field"] == "signal")
"""
When change the time machine script editor contents to:
"""
Muffin Man
"""
When click the time machine cell edit submit button
Then the time machine preview canvas is not present
Then the time machine preview canvas axes are not present
Then the time machine empty graph error message is:
"""
type error 1:1-1:7: undefined identifier "Muffin"
"""
When click the cell edit save button
Then the cell named "Kliky" contains a graph error
# Popover has been replaced with message in cell
#When hover over the error icon of the cell "Kliky"
Then the cell error message of the cell named "Kliky" is:
"""
type error 1:1-1:7: undefined identifier "Muffin"
"""
@tested
Scenario: Exercise Add functions - Query Builder
When toggle context menu of dashboard cell named "Kliky"
When click cell content popover configure
When click the cell edit Query Builder button
When click the cell edit Query Builder confirm button
# TODO Clean up garbage from issue 16731
When close all time machine builder cards
When unselect any tags in time machine builder card "1"
When click the time machine bucket selector item "qa"
When click the tag "beat" in builder card "1"
Then the time machine cell edit submit button is enabled
When click the tag "pulse" in builder card "2"
When click the time machine cell edit submit button
When get time machine preview canvas
When get time machine preview axes
When click the time machine query builder function duration input
When click the query builder function duration suggestion "auto (10s)"
When click the query builder function "mean"
When click the time machine cell edit submit button
Then the time machine preview canvas has changed
Then the time machine preview axes have changed
When get time machine preview canvas
When get time machine preview axes
When click the time machine query builder function duration input
When click the query builder function duration suggestion "1m"
When click the time machine cell edit submit button
Then the time machine preview canvas has changed
Then the time machine preview axes have changed
When get time machine preview canvas
When get time machine preview axes
When click the query builder function "mean"
When click the query builder function "derivative"
When click the time machine cell edit submit button
Then the time machine preview canvas has changed
Then the time machine preview axes have changed
When get time machine preview canvas
When get time machine preview axes
When click the query builder function "derivative"
When click the time machine cell edit submit button
Then the time machine preview canvas has changed
Then the time machine preview axes have changed
When click dashboard cell save button
Then the cell named "Kliky" contains a graph
@tested
Scenario: Edit Query - Exercise functions
When get the current graph of the cell "Kliky"
When toggle context menu of dashboard cell named "Kliky"
When click cell content popover configure
When click the cell edit Script Editor button
Then the time machine script editor contains
"""
from(bucket: "qa")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["_measurement"] == "beat")
|> filter(fn: (r) => r["_field"] == "pulse")
"""
Then the time machine query edit function categories are displayed:
"""
Aggregates,Inputs,Type Conversions,Selectors,Transformations,Outputs,Miscellaneous,Tests
"""
When filter the time machine query edit function list with "average"
Then the following function are visible in the time machine function list:
"""
exponentialMovingAverage,movingAverage,timedMovingAverage,highestAverage,lowestAverage
"""
Then the following function are not visible in the time machine function list:
"""
doubleEMA,pearsonr,sum,first,lowestCurrent
"""
When clear the time machine query edit function list filter
Then the following function are visible in the time machine function list:
"""
doubleEMA,pearsonr,sum,first,lowestCurrent
"""
When hover over time machine query edit function "skew"
Then the time machine query edit function popup description contains:
"""
Outputs the skew of non-null records as a float.
"""
Then the time machine query edit function popup snippet contains:
"""
skew(column: "_value")
"""
When hover over the time machine query editor submit button
Then the time machine query edit function popup is not visible
When click dashboard cell edit cancel button
@tested
Scenario: Edit Query - Add a Function
When get the current graph of the cell "Kliky"
When toggle context menu of dashboard cell named "Kliky"
When click cell content popover configure
When click the cell edit Script Editor button
Then the time machine script editor contains
"""
from(bucket: "qa")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["_measurement"] == "beat")
|> filter(fn: (r) => r["_field"] == "pulse")
"""
When get time machine preview canvas
When get time machine preview axes
When click the time machine flux editor
#When click the time machine query editor function "aggregateWindow"
When click inject the time machine query editor function "aggregateWindow"
Then the time machine script editor contains
"""
from(bucket: "qa")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["_measurement"] == "beat")
|> filter(fn: (r) => r["_field"] == "pulse")
|> aggregateWindow(every: v.windowPeriod, fn: mean)
"""
# In CircleCi function popup can obscure the submit button
#When click the time machine flux editor
When click the filter functions input
When click the time machine cell edit submit button
Then the time machine preview canvas has changed
When click the cell edit save button
Then the graph of the cell "Kliky" has changed
@error-collateral
Scenario: Change time range
When get the current graph of the cell "Kliky"
When toggle context menu of dashboard cell named "Kliky"
When click cell content popover configure
When get time machine preview canvas
When get time machine preview axes
When click the cell edit Time Range Dropdown
When select the cell edit Time Range "past24h"
#When click the time machine flux editor
When click the filter functions input
Then the time machine preview canvas has changed
Then the time machine preview axes have changed
When click the cell edit save button
Then the graph of the cell "Kliky" has changed
Then the dashboard Time Range Dropdown selected contains "Past 24h"
#When click the dashboard Time Range Dropdown
#When select dashboard Time Range "24h"
#Then the graph of the cell "Kliky" has changed
@error-collateral
Scenario: View raw data
When toggle context menu of dashboard cell named "Kliky"
When click cell content popover configure
When get time machine preview canvas
When get time machine preview axes
Then the time machine raw data table is not present
When click time machine raw data toggle
Then the time machine raw data table is present
Then the time machine preview canvas is not present
When click time machine raw data toggle
Then the time machine preview canvas has changed
Then the time machine raw data table is not present
When click dashboard cell edit cancel button
Then the cell named "Kliky" contains a graph
@error-collateral
Scenario: Download results as CSV
When remove files ".*chronograf_data.csv" if exists
When toggle context menu of dashboard cell named "Kliky"
When click cell content popover configure
When click time machine download CSV
Then a file matching ".*chronograf_data.csv" exists
When verify first CSV file matching ".*chronograf_data.csv" as containing
"""
{ "_time": "type:date", "_value": "type:double", "_field": "pulse", "_measurement": "beat", "test": "generic" }
"""
When click dashboard cell edit cancel button
Then the cell named "Kliky" contains a graph
@error-collateral
Scenario: Refresh Rates
#earlier signin may have timed out
When API sign in user "DEFAULT"
When toggle context menu of dashboard cell named "Kliky"
When click cell content popover configure
When get time machine preview canvas
When get time machine preview axes
When wait "20" seconds
When generate a line protocol testdata for user "DEFAULT" based on:
"""
{ "points": 5, "measurement":"pulse", "start": "-5m", "algo": "log", "prec": "sec", "name": "beat"}
"""
Then the time machine force refresh button is present
Then the time machine autorefresh dropdown list is set to "Paused"
When click time machine force refresh
Then the time machine preview canvas has changed
Then the time machine preview axes have changed
When get time machine preview canvas
When get time machine preview axes
When click time machine autorefresh dropdown
When select the time machine autorefresh rate "10s"
Then the time machine force refresh button is not present
When generate a line protocol testdata for user "DEFAULT" based on:
"""
{ "points": 5, "measurement":"pulse", "start": "-5m", "algo": "log", "prec": "sec", "name": "beat"}
"""
When wait "20" seconds
Then the time machine preview canvas has changed
Then the time machine preview axes have changed
When click time machine autorefresh dropdown
When select the time machine autorefresh rate "Paused"
When get time machine preview canvas
When get time machine preview axes
When generate a line protocol testdata for user "DEFAULT" based on:
"""
{ "points": 5, "measurement":"pulse", "start": "-5m", "algo": "log", "prec": "sec", "name": "beat"}
"""
When wait "20" seconds
Then the time machine preview canvas has not changed

View File

@ -0,0 +1,350 @@
@feature-dashboards
@dashboards-dashboard
Feature: Dashboards - Dashboard - Base
As a user I want to Read Create Update and Delete a Dashboard
So that I can view specific Influxdbv2 data
@tested
Scenario: Load Initial Dashboard view
Given I reset the environment
Given run setup over REST "DEFAULT"
When open the signin page
When UI sign in user "DEFAULT"
When click nav menu item "Dashboards"
# When hover over the "Dashboards" menu item
# When click nav sub menu "Dashboards"
Then the Dashboards page is loaded
When API sign in user "DEFAULT"
When API create a label "Cesko" described as "Pravda vitezi" with color "#AAFFAA" for user "DEFAULT"
When API create a label "Mesto" described as "Matka mest" with color "#FFAAAA" for user "DEFAULT"
When generate a line protocol testdata for user "DEFAULT" based on:
"""
{ "points": 120, "measurement":"level", "start": "-30d", "algo": "hydro", "prec": "sec", "name": "foo"}
"""
When generate a line protocol testdata for user "DEFAULT" based on:
"""
{ "points": 120, "measurement":"beat", "start": "-30d", "algo": "sine", "prec": "sec", "name": "bar"}
"""
When click the empty Create dashboard dropdown button
When click the create dashboard item "New Dashboard"
Then the new dashboard page is loaded
Then the empty dashboard contains a documentation link
Then the empty dashboard contains Add a Cell button
When name dashboard "про́бный прибо́ров"
@error-collateral
Scenario: Exercise Dashboard Dropdowns
When click dashboard time locale dropdown
Then the active dashboard dropdown contains items:
"""
Local,UTC
"""
When click dashboard refresh dropdown
Then the active dashboard dropdown contains dividers:
"""
Refresh
"""
Then the active dashboard dropdown contains items:
"""
Paused,5s,10s,15s,30s,60s
"""
When click dashboard time range dropdown
Then the active dashboard dropdown contains dividers:
"""
Time Range
"""
Then the active dashboard dropdown contains items:
"""
Custom Time Range,Past 5m,Past 15m,Past 1h,Past 6h,Past 12h,Past 24h,Past 2d,Past 7d,Past 30d
"""
@tested
Scenario: Create Cell
When click the empty create cell button
Then the cell edit overlay is loaded as "Name this Cell"
When name dashboard cell "вре́менный"
When click dashboard cell edit cancel button
Then there is no dashboard cell named "вре́менный"
When click the empty create cell button
Then the cell edit overlay is loaded as "Name this Cell"
When name dashboard cell "вре́менный"
When click dashboard cell save button
Then the dashboard contains a cell named "вре́менный"
#Currently failing due to issue #16619
@tested
Scenario: Add Note to Cell
When toggle context menu of dashboard cell named "вре́менный"
When click cell content popover add note
Then the edit note popup is loaded
Then dismiss the popup
Then popup is not loaded
When toggle context menu of dashboard cell named "вре́менный"
When click cell content popover add note
Then click popup cancel simple button
Then popup is not loaded
When toggle context menu of dashboard cell named "вре́менный"
When click cell content popover add note
When enter the cell note popup CodeMirror text:
"""
__Шинель__\n
_Гоголь_\n
В департаменте но лучше не называть в каком департаменте...
"""
Then the cell note popup Markdown preview panel contains
"""
В департаменте но лучше не называть в каком департаменте...
"""
When click the cell note popup save button
Then popup is not loaded
Then the cell named "вре́менный" has a note indicator
When click the note indicator of the "вре́менный" cell
Then the cell note popover contains:
"""
В департаменте но лучше не называть в каком департаменте...
"""
When click the cell title "вре́менный"
Then the cell note popover is not loaded
When toggle context menu of dashboard cell named "вре́менный"
Then the cell content popover has item edit note
When click the cell title "вре́менный"
Then the cell content popover is not loaded
@tested
Scenario: Edit Cell Note
When toggle context menu of dashboard cell named "вре́менный"
When click cell content popover add note
Then the edit note popup is loaded
Then the cell note popup Code Mirror text contains:
"""
В департаменте но лучше не называть в каком департаменте...
"""
Then the cell note popup Code Mirror text contains:
"""
_Гоголь_
"""
Then the cell note popup Code Mirror text contains:
"""
__Шинель__
"""
Then the cell note popup Markdown preview panel contains
"""
В департаменте но лучше не называть в каком департаменте...
"""
When clear the cell note popup Code Mirror text
Then the cell note popup markup preview panel has no text
When enter the cell note popup CodeMirror text:
"""
__LE MANTEAU__\n
_Nikolaï Gogol_\n
Dans une administration russe... mieux vaut ne pas dire le nom de cette administration ...
"""
Then the cell note popup Markdown preview panel contains
"""
Dans une administration russe... mieux vaut ne pas dire le nom de cette administration ...
"""
When click the cell note popup save button
Then popup is not loaded
When click the note indicator of the "вре́менный" cell
Then the cell note popover contains:
"""
Dans une administration russe... mieux vaut ne pas dire le nom de cette administration ...
"""
When click the cell title "вре́менный"
Then the cell content popover is not loaded
@error-collateral
Scenario: Move cell
When get metrics of cell named "вре́менный"
When move the cell named "вре́менный" by "{ "dx": "+400", "dy": "+200" }"
# Y is 0 below because of float to top feature - cells float to top row when repositioned
Then the location of the cell named "вре́менный" is changed by "{ "dx": "+400", "dy": "0" }"
When get metrics of cell named "вре́менный"
When move the cell named "вре́менный" by "{ "dx": "-400", "dy": "+200" }"
Then the location of the cell named "вре́менный" is changed by "{ "dx": "-400", "dy": "0" }"
When get metrics of cell named "вре́менный"
When move the cell named "вре́менный" by "{ "dx": "0", "dy": "+200" }"
Then the location of the cell named "вре́менный" is changed by "{ "dx": "0", "dy": "0" }"
When move the cell named "вре́менный" by "{ "dx": "+400", "dy": "+200" }"
When get metrics of cell named "вре́менный"
When click nav menu item "home"
#When hover over the "Dashboards" menu item
#When click nav menu item "home"
When click nav menu item "Dashboards"
#When hover over the "Dashboards" menu item
#When click nav sub menu "Dashboards"
When click the dashboard name "про́бный прибо́ров"
Then the dashboard named "про́бный прибо́ров" is loaded
Then the location of the cell named "вре́менный" is unchanged
When move the cell named "вре́менный" by "{ "dx": "-400", "dy": "0" }"
@tested
Scenario: Edit Cell - Simple
Then the cell named "вре́менный" contains the empty graph message
When toggle context menu of dashboard cell named "вре́менный"
When click cell content popover configure
Then the cell edit overlay is loaded as "вре́менный"
When get the current cell edit preview graph
When click the cell edit Time Range Dropdown
When select the cell edit Time Range "Past 30d"
When click the cell edit Script Editor button
When paste into cell edit Script Editor
"""
from(bucket: "qa")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r._measurement == "foo")
|> filter(fn: (r) => r._field == "level")
"""
When click the time machine cell edit submit button
Then the time machine cell edit preview graph is shown
Then the cell edit preview graph is changed
When click the cell edit save button
When click the dashboard Time Range Dropdown
When select dashboard Time Range "30d"
Then the cell named "вре́менный" contains a graph
@error-collateral
Scenario: Resize Cell
When get the current graph of the cell "вре́менный"
When get metrics of cell named "вре́менный"
When resize the cell name "вре́менный" by "{ "dw": "+300", "dh": "+100" }"
Then the graph of the cell "вре́менный" has changed
Then size of the cell named "вре́менный" has changed by "{ "dw": "+300", "dh": "+100" }"
# Leave then return check TODO after issue 16180 fixed
#When get the current graph of the cell "вре́менный"
#When get metrics of cell named "вре́менный"
#When hover over the "Dashboards" menu item
#When click nav menu item "home"
#When hover over the "Dashboards" menu item
#When click nav sub menu "Dashboards"
#When click the dashboard name "про́бный прибо́ров"
#Then the dashboard named "про́бный прибо́ров" is loaded
#Then the size of the of the cell named "вре́менный" is unchangd
@error-collateral
Scenario: Hover Cell Graph
When hover over the graph of the cell named "вре́менный"
Then the cell graph data point infobox is visible
@error-collateral
Scenario: Zoom Cell horizontal
When get the current graph of the cell "вре́менный"
When move horizontally to "2/5" of graph cell named "вре́менный"
When drag horizontally to "3/5" of graph cell named "вре́менный"
Then the graph of the cell "вре́менный" has changed
@error-collateral
Scenario: Unzoom Cell
When get the current graph of the cell "вре́менный"
When Click at the point "{"x": "1/2", "y": "1/2"}" of graph cell named "вре́менный"
Then the graph of the cell "вре́менный" has changed
@error-collateral
Scenario: Zoom Cell vertical
When get the current graph of the cell "вре́менный"
When move vertically to "2/5" of graph cell named "вре́менный"
When drag vertically to "3/5" of graph cell named "вре́менный"
Then the graph of the cell "вре́менный" has changed
@error-collateral
Scenario: Unzoom Cell 2
When get the current graph of the cell "вре́менный"
When Click at the point "{"x": "1/2", "y": "1/2"}" of graph cell named "вре́менный"
Then the graph of the cell "вре́менный" has changed
@tested
Scenario: Rename Cell
When toggle context menu of dashboard cell named "вре́менный"
When click cell content popover configure
Then the cell edit overlay is loaded as "вре́менный"
When click on the cell edit name
When change the cell edit name to "dočasný"
When click the cell edit save button
Then the cell named "dočasný" is visible in the dashboard
@tested
Scenario: Clone Cell
When toggle context menu of dashboard cell named "dočasný"
When click cell edit content popover clone
Then the cell named "dočasný (Clone)" is visible in the dashboard
When toggle context menu of dashboard cell named "dočasný (Clone)"
When click cell content popover configure
When clear the cell edit Script Editor
When paste into cell edit Script Editor
"""
from(bucket: "qa")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r._measurement == "foo")
|> filter(fn: (r) => r._field == "level")
|> movingAverage(n: 5)
"""
When click the time machine cell edit submit button
When click on the cell edit name
When change the cell edit name to "klouzavý průměr"
When click the cell edit save button
Then the graph of the cell "dočasný" differs from "klouzavý průměr"
# Following step fails due to issue #16619
When click the note indicator of the "klouzavý průměr" cell
Then the cell note popover contains:
"""
Dans une administration russe... mieux vaut ne pas dire le nom de cette administration ...
"""
When click the cell title "klouzavý průměr"
Then the cell content popover is not loaded
@error-collateral
Scenario: Two cells column to row
When get metrics of cell named "dočasný"
When get metrics of cell named "klouzavý průměr"
When move the cell named "dočasný" by "{ "dx": "+300", "dy": "0" }"
Then the location of the cell named "klouzavý průměr" is changed by "{ "dx": "0", "dy": "-380" }"
@error-collateral
Scenario: Two cells row to column
When get metrics of cell named "dočasný"
When get metrics of cell named "klouzavý průměr"
When move the cell named "dočasný" by "{ "dx": "-300", "dy": "0" }"
Then the location of the cell named "klouzavý průměr" is changed by "{ "dx": "0", "dy": "+380" }"
@error-collateral
Scenario: Two cells enlarge first into second
When get metrics of cell named "klouzavý průměr"
When move the cell named "dočasný" by "{ "dx": "+300", "dy": "0" }"
Then the location of the cell named "klouzavý průměr" is changed by "{ "dx": "0", "dy": "-380" }"
When get metrics of cell named "dočasný"
When get metrics of cell named "klouzavý průměr"
When resize the cell name "klouzavý průměr" by "{ "dw": "+300", "dh": "0" }"
Then the location of the cell named "dočasný" is changed by "{ "dx": "0", "dy": "+380" }"
Then size of the cell named "klouzavý průměr" has changed by "{ "dw": "+300", "dh": "0" }"
@error-collateral
Scenario: Two cells reduce first when above second
When get metrics of cell named "dočasný"
When get metrics of cell named "klouzavý průměr"
When resize the cell name "klouzavý průměr" by "{ "dw": "-300", "dh": "0" }"
Then the location of the cell named "dočasný" is changed by "{ "dx": "0", "dy": "-380" }"
Then size of the cell named "klouzavý průměr" has changed by "{ "dw": "-300", "dh": "0" }"
@error-collateral
Scenario: Two cells column to row - Moved cell drops down
When get metrics of cell named "dočasný"
When get metrics of cell named "klouzavý průměr"
When move the cell named "dočasný" by "{ "dx": "-150", "dy": "150" }"
Then the location of the cell named "dočasný" is changed by "{ "dx": "-150", "dy": "+380" }"
@tested
Scenario Outline: Delete Cell
When toggle context menu of dashboard cell named "<NAME>"
When click cell content popover delete
When click cell content popover delet confirm
# following should be notification-primary [data-testid=notification-primary]
#Then the success notification contains "Cell deleted from dashboard"
Then the primary notification contains "Cell deleted from dashboard"
Then close all notifications
Then the cell named "<NAME>" is no longer present
Examples:
|NAME|
|klouzavý průměr|
|dočasný |
# TODO - Dark Mode / Light Mode

View File

@ -0,0 +1,309 @@
@feature-dashboards
@dashboards-dashboards
Feature: Dashboards - Base
As a user I want to Read Create Update and Delete Dashboards
So that I can organize my data in Influxdbv2
@tested
Scenario: Load dashboards page
Given I reset the environment
Given run setup over REST "DEFAULT"
When open the signin page
When UI sign in user "DEFAULT"
When click nav menu item "Dashboards"
#When hover over the "Dashboards" menu item
#When click nav sub menu "Dashboards"
Then the Dashboards page is loaded
When API sign in user "DEFAULT"
When API create a label "Cesko" described as "Pravda vitezi" with color "#AAFFAA" for user "DEFAULT"
When API create a label "Mesto" described as "Matka mest" with color "#FFAAAA" for user "DEFAULT"
When generate a line protocol testdata for user "DEFAULT" based on:
"""
{ "points": 120, "measurement":"level", "start": "-30d", "algo": "hydro", "prec": "sec", "name": "hydro"}
"""
When generate a line protocol testdata for user "DEFAULT" based on:
"""
{ "points": 120, "measurement":"beat", "start": "-30d", "algo": "sine", "prec": "sec", "name": "sine"}
"""
@tested
Scenario: Create new dashboard
When click the empty Create dashboard dropdown button
Then the empty create dashboard dropdown list contains
"""
New Dashboard,Import Dashboard,From a Template
"""
When click the create dashboard item "New Dashboard"
Then the new dashboard page is loaded
When click nav menu item "Dashboards"
#When hover over the "Dashboards" menu item
#When click nav sub menu "Dashboards"
Then the empty Create dashboard dropdown button is not present
Then there is a dashboard card named "Name this Dashboard"
@tested
Scenario: Rename dashboard from card
When hover over dashboard card named "Name this Dashboard"
Then the export button for the dashboard card "Name this Dashboard" is visible
Then the clone button for the dashboard card "Name this Dashboard" is visible
Then the delete button for the dashboard card "Name this Dashboard" is visible
When hover over dashboard card name "Name this Dashboard"
When click the edit dashboard card name button for "Name this Dashboard"
When clear the name input of the dashboard card "Name this Dashboard"
When enter the new name "Mercure" in the name input of the dashboard card "Name this Dashboard"
When press the "ENTER" key
Then there is a dashboard card named "Mercure"
Then there is no dashboard card named "Name this Dashboard"
@tested
Scenario: Add description to dashboard
Then the description for card "Mercure" contains "No description"
When hover over description of the dashboard card "Mercure"
When click the edit description button for the dashboard card "Mercure"
When enter into the dashboard card "Mercure" the description:
"""
le dieu du commerce dans la mythologie romaine
"""
When press the "ENTER" key
Then the description for card "Mercure" contains "le dieu du commerce dans la mythologie romaine"
@tested
Scenario: Add Labels to dashboard
# Issue 16529 - possible design change
When click empty label for the dashboard card "Mercure"
Then the label "Cesko" in the popover selector is visible
Then the label "Mesto" in the popover selector is visible
Then the create new label item is not visible in the popover
When enter "Slovensko" in the popover label selector filter
Then the create new label item is visible in the popover
Then there are "0" label pills in the select label popover
When click the new label item in the add labels popover
Then the create Label popup is loaded
When dismiss the popup
Then popup is not loaded
Then the add label popover is not present
When click the add label button for the dashboard card "Mercure"
# Issue 16528 - control values not cleared
Then there are "2" label pills in the select label popover
When enter "Slovensko" in the popover label selector filter
When click the new label item in the add labels popover
When click the label popup Create Label button
Then popup is not loaded
Then the dashboard card "Mercure" has the label "Slovensko"
When hover over the label "Slovensko" of the dashboard card "Mercure"
When click remove label "Slovensko" from the dashboard card "Mercure"
Then the dashboard card "Mercure" labels empty message is visible
Then the label "Slovenkso" of the dashboard card "Mercure" is not present
When click the add label button for the dashboard card "Mercure"
Then there are "3" label pills in the select label popover
When enter "sko" in the popover label selector filter
Then there are "2" label pills in the select label popover
Then the label "Mesto" is not present in the popover selector
When clear the popover label selector filter
Then there are "3" label pills in the select label popover
# TODO use escape key once #17853 is resolved
#When press the "ESCAPE" key
When click the dashboards filter input
Then the add label popover is not present
#TODO add check for issue #17964
@tested
Scenario Outline: Create new Dashboard
When click create dashboard control
When click the create dashboard item "New Dashboard"
When name dashboard "<NAME>"
When click nav menu item "Dashboards"
#When hover over the "Dashboards" menu item
#When click nav sub menu "Dashboards"
Then there is a dashboard card named "<NAME>"
Examples:
|NAME|
|Terre|
|Venus|
|Jupiter|
|Mars |
@tested
Scenario: Access Dashboard from home page
When hover over the "home" menu item
When click nav menu item "home"
Then the dashboards panel contains links:
"""
Mercure,Venus,Terre,Mars,Jupiter
"""
When click the dashboards panel link "Mercure"
Then the dashboard named "Mercure" is loaded
@tested
Scenario: Filter Dashboard Cards
When click nav menu item "Dashboards"
#When hover over the "Dashboards" menu item
#When click nav sub menu "Dashboards"
Then the dashboards page contains the cards:
"""
Mercure,Venus,Terre,Mars,Jupiter
"""
When enter the term "ter" in the dashboards filter
Then the dashboards page contains the cards:
"""
Terre,Jupiter
"""
Then the dashboards page does not contain the cards:
"""
Mercure,Venus,Mars
"""
When clear the dashboards filter
Then the dashboards page contains the cards:
"""
Mercure,Venus,Terre,Mars,Jupiter
"""
@error-collateral
Scenario: Import Dashboard from file
When click create dashboard control
When click the create dashboard item "Import Dashboard"
Then the Import Dashboard popup is loaded
When upload the import dashboard file "etc/test-data/alpha_dashboard.json"
Then the import dashboard drag and drop header contains success "etc/test-data/alpha_dashboard.json"
When click the Import Dashboard button
Then popup is not loaded
Then the success notification contains "Successfully imported dashboard."
Then close all notifications
Then there is a dashboard card named "Alpha Centauri"
@tested
Scenario: Import Dashboard paste json
When click create dashboard control
When click the create dashboard item "Import Dashboard"
Then the Import Dashboard popup is loaded
When click the Import Dashboard popup radio button Paste Json
Then the Import Dashboard file upload control is not present
When paste contents of file "etc/test-data/tau_ceti_dashboard.json" into the JSON textarea
When click the Import Dashboard button
Then popup is not loaded
Then the success notification contains "Successfully imported dashboard."
Then close all notifications
Then there is a dashboard card named "Tau Ceti"
@tested
Scenario: Create Dashboard from template
When create a new template from the file "etc/test-data/sine-test-template.json" for user "DEFAULT"
When click nav menu item "Dashboards"
#When hover over the "Dashboards" menu item
#When click nav sub menu "Dashboards"
Then the Dashboards page is loaded
When click create dashboard control
When click the create dashboard item "From a Template"
Then the Create Dashboard from Template popup is loaded
Then the Dashboard from Template create button is disabled
When click Dashboard from Template popup cancel button
Then popup is not loaded
When click create dashboard control
When click the create dashboard item "From a Template"
Then dismiss the popup
Then popup is not loaded
When click create dashboard control
When click the create dashboard item "From a Template"
When click the template item "Sinusoid test data-Template"
Then the template preview cell "Beat goes on" is visible
When click Dashboard from Template create button
Then there is a dashboard card named "Sinusoid test data"
@tested
Scenario: Sort Dashboards by Name
When close all notifications
Then the dashboards are sorted as:
"""
Alpha Centauri,Jupiter,Mars,Mercure,Sinusoid test data,Tau Ceti,Terre,Venus
"""
When click dashboards sort type dropdown
When click dashboards sort by "Name Desc"
#When click dashboards sort by name
Then the dashboards are sorted as:
"""
Venus,Terre,Tau Ceti,Sinusoid test data,Mercure,Mars,Jupiter,Alpha Centauri
"""
When click dashboards sort type dropdown
When click dashboards sort by "Name Asc"
Then the dashboards are sorted as:
"""
Alpha Centauri,Jupiter,Mars,Mercure,Sinusoid test data,Tau Ceti,Terre,Venus
"""
# Scenario: Sort Dashboards by Modified time
# TODO - implement after issue #15610 is resolved
@error-collateral
Scenario: Export Dashboard as Template
When hover over dashboard card named "Alpha Centauri"
When click export of the dashboard card named "Alpha Centauri"
When click confirm export of the dashboard card "Alpha Centauri"
When click Export Dashboard popup Save as Template
Then the success notification contains "Successfully saved dashboard as template"
Then a REST template document for user "DEFAULT" titled "Alpha Centauri-Template" exists
Then close all notifications
@error-collateral
Scenario: Export Dashboard copy to clipboard
When hover over dashboard card named "Jupiter"
When click export of the dashboard card named "Jupiter"
When click confirm export of the dashboard card "Jupiter"
When click Export Dashboard popup Copy to Clipboard
Then the success notification contains "Copied dashboard to the clipboard"
Then close all notifications
#N.B. clipboard not accessible for comparison in headless mode
When click the Export Dashboard dismiss button
Then popup is not loaded
@error-collateral
Scenario: Export Dashboard to file
When remove file "tau_ceti.json" if exists
When hover over dashboard card named "Tau Ceti"
When click export of the dashboard card named "Tau Ceti"
When click confirm export of the dashboard card "Tau Ceti"
Then the Export Dashboard popup is loaded
When click the Export Dashboard dismiss button
Then popup is not loaded
When hover over dashboard card named "Tau Ceti"
When click export of the dashboard card named "Tau Ceti"
When click confirm export of the dashboard card "Tau Ceti"
When click Export Dashboard popup Download JSON for "tau_ceti.json"
Then popup is not loaded
Then the file "tau_ceti.json" has been downloaded
When remove file "tau_ceti.json" if exists
@tested
Scenario: Clone Dashboard
When hover over dashboard card named "Tau Ceti"
When click clone of the dashboard card named "Tau Ceti"
When click the clone confirm of dashboard card "Tau Ceti"
Then the dashboard named "Tau Ceti (clone 1)" is loaded
Then the dashboard contains a cell named "Hydro derivative"
Then the dashboard contains a cell named "Sinusoid sum - missed points"
Then the dashboard contains a cell named "Note"
When click nav menu item "Dashboards"
#When hover over the "Dashboards" menu item
#When click nav sub menu "Dashboards"
Then there is a dashboard card named "Tau Ceti (clone 1)"
@tested
Scenario Outline: Delete dashboards
When hover over dashboard card named "<NAME>"
When click delete of dashboard card "<NAME>"
When click delete confirm of dashboard card "<NAME>"
Then the success notification contains "Dashboard <NAME> deleted successfully"
When close all notifications
Then there is no dashboard card named "<NAME>"
Examples:
|NAME|
|Mercure|
|Terre|
|Venus|
|Jupiter|
|Mars|
|Alpha Centauri|
|Tau Ceti|
|Tau Ceti (clone 1)|
|Sinusoid test data|

View File

@ -0,0 +1,91 @@
@feature-dashboards
@dashboards-noteCell
Feature: Dashboards - Dashboard - Note Cell
As a user I want to Add a Note Cell
So that I can provide specific static information
# Show note when query return no data makes little sense
# Uses Code Mirror still
Scenario: Create basic dashboard for notes
Given I reset the environment
Given run setup over REST "DEFAULT"
When open the signin page
When UI sign in user "DEFAULT"
When click nav menu item "Dashboards"
# When hover over the "Dashboards" menu item
# When click nav sub menu "Dashboards"
Then the Dashboards page is loaded
When click the empty Create dashboard dropdown button
When click the create dashboard item "New Dashboard"
Then the new dashboard page is loaded
Then the empty dashboard contains a documentation link
Then the empty dashboard contains Add a Cell button
When name dashboard "Rumcajs"
#N.B. expecting the toggle "show when query returns no data" to not be present
# TODO #17412 - add check that toggle not present to detect any regression
Scenario: Exercise Add Note popup controls
When click dashboard add note button
# N.B. add guide link check
Then main "Add" note popup is loaded
#Then the query no data toggle is not present
Then dismiss the popup
Then popup is not loaded
When click dashboard add note button
Then click popup cancel simple button
Then popup is not loaded
Scenario: Add Note with markdown
When click dashboard add note button
When enter the cell note popup CodeMirror text:
"""
# Večerníček
## Rumcajs
**Loupežník Rumcajs** je _pohádková postava_ z [Večerníčku](https://cs.wikipedia.org/wiki/Ve%C4%8Dern%C3%AD%C4%8Dek), jejíž příběhy následně vyšly i knižně.
> Jak Rumcajs vysadil duhu na nebe
* Rumcajs
* Manka
* Cipísek
1. Alpha
1. Beta
1. Gamma
"""
Then the main note popup markdown preview panel contains a "h1" tag with "Večerníček"
Then the main note popup markdown preview panel contains a "h2" tag with "Rumcajs"
Then the main note popup markdown preview panel contains a "strong" tag with "Loupežník Rumcajs"
Then the main note popup markdown preview panel contains a "em" tag with "pohádková postava"
Then the main note popup markdown preview panel contains a "blockquote" tag with "Jak Rumcajs vysadil duhu na nebe"
Then the main note popup markdown preview panel contains a "ul li" tag with "Rumcajs"
Then the main note popup markdown preview panel contains a "ol li" tag with "Alpha"
When click the cell note popup save button
Then popup is not loaded
Then the note cell contains a "h1" tag with "Večerníček"
Then the note cell contains a "h2" tag with "Rumcajs"
Then the note cell contains a "strong" tag with "Loupežník Rumcajs"
Then the note cell contains a "em" tag with "pohádková postava"
Then the note cell contains a "blockquote" tag with "Jak Rumcajs vysadil duhu na nebe"
Then the note cell contains a "ul li" tag with "Rumcajs"
Then the note cell contains a "ol li" tag with "Alpha"
@tested
Scenario: Edit note
When toggle context menu of dashboard cell named "Note"
When click cell content popover edit note
Then main "Edit" note popup is loaded
# TODO edit text and verify - need to push on to higher priority tests
When click popup cancel simple button
Scenario: Delete note
When toggle context menu of dashboard cell named "Note"
When click cell content popover delete
When click cell content popover delet confirm
Then the cell named "Note" is no longer present

View File

@ -0,0 +1,342 @@
@feature-dashboards
@dashboards-variables
Feature: Dashboards - Dashboard - Variables
As a user I want to Add Variables to cell Queries
So that I can view specific Influxdbv2 data sets
@tested
@tested
Scenario: Load Cell Edit View
Given I reset the environment
Given run setup over REST "DEFAULT"
When open the signin page
When clear browser storage
When UI sign in user "DEFAULT"
When click nav menu item "Dashboards"
#When hover over the "Dashboards" menu item
#When click nav sub menu "Dashboards"
Then the Dashboards page is loaded
When API sign in user "DEFAULT"
When generate a line protocol testdata for user "DEFAULT" based on:
"""
{ "points": 2880, "measurement":"pulse", "start": "-48h", "algo": "log", "prec": "sec", "name": "beat"}
"""
When generate a line protocol testdata for user "DEFAULT" based on:
"""
{ "points": 7200, "measurement":"signal", "start": "-30d", "algo": "sine", "prec": "sec", "name": "foo"}
"""
When generate a line protocol testdata for user "DEFAULT" based on:
"""
{ "points": 2880, "measurement":"sema", "start": "-48h", "algo": "log", "prec": "sec", "name": "beat"}
"""
When generate a line protocol testdata for user "DEFAULT" based on:
"""
{ "points": 2880, "measurement":"znacka", "start": "-48h", "algo": "log", "prec": "sec", "name": "beat"}
"""
When generate a line protocol testdata for user "DEFAULT" based on:
"""
{ "points": 2880, "measurement":"mots", "start": "-48h", "algo": "dico", "data": ["pulse","sema","znacka","wombat"], "prec": "sec", "name": "slovo"}
"""
When generate a line protocol testdata for user "DEFAULT" based on:
"""
{ "points": 2880, "measurement":"worten", "start": "-48h", "algo": "dico", "data": ["alpha","beta"], "prec": "sec", "name": "logo"}
"""
When create the "csv" variable "APIVAR" with default "[ "pulse" ]" for user "DEFAULT" with values:
"""
[ "pulse", "sema", "znacka", "hele" ]
"""
When create the "query" variable "POKUS" with default "[ "pulse" ]" for user "DEFAULT" with values:
"""
{"language":"flux","query":"from(bucket: \"qa\")\n |> range(start: -1d, stop: now() )\n |> filter(fn: (r) => r._measurement == \"slovo\")\n |> filter(fn: (r) => r._field == \"mots\")\n |> unique(column: \"_value\")"}
"""
When create the "map" variable "KARTA" with default "[ "poppy" ]" for user "DEFAULT" with values:
"""
{ "poppy":"pulse","salvia":"sema","zinnia":"znacka","daisy":"duck" }
"""
When click the empty Create dashboard dropdown button
When click the create dashboard item "New Dashboard"
Then the new dashboard page is loaded
Then the empty dashboard contains a documentation link
Then the empty dashboard contains Add a Cell button
When name dashboard "Semiotic"
@error-collateral
Scenario: Create Basic Cell for Variables
When click the empty create cell button
Then the cell edit overlay is loaded as "Name this Cell"
When name dashboard cell "Semantic"
When click the time machine bucket selector item "qa"
When click the tag "beat" in builder card "1"
Then the time machine cell edit submit button is enabled
When click the tag "pulse" in builder card "2"
When click the time machine cell edit submit button
Then the time machine cell edit preview graph is shown
Then the time machine cell edit preview axes are shown
When click dashboard cell save button
Then the dashboard contains a cell named "Semantic"
@error-collateral
Scenario: Edit Query - Filter variables
When toggle context menu of dashboard cell named "Semantic"
When click cell content popover configure
When click the cell edit Script Editor button
Then the time machine script editor contains
"""
from(bucket: "qa")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["_measurement"] == "beat")
|> filter(fn: (r) => r["_field"] == "pulse")
"""
When click the time machine script editor variables tab
Then the time machine variables list contains
"""
APIVAR,POKUS,KARTA
"""
When enter the value "AR" in the time machine variables filter
Then the time machine variables list contains
"""
APIVAR,KARTA
"""
Then the time machine variables list does not contain
"""
POKUS
"""
When clear the time machine variables filter
Then the time machine variables list contains
"""
APIVAR,POKUS,KARTA
"""
When click dashboard cell edit cancel button
Then the dashboard contains a cell named "Semantic"
# time-machine--bottom
# switch-to-script-editor
# Queries
# Script Editor
# Variables
@error-collateral
Scenario: Add CSV variable to script
When toggle context menu of dashboard cell named "Semantic"
When click cell content popover configure
When click the cell edit Script Editor button
# Move to end
When send keys "CTRL+END" to the time machine flux editor
# Send cursor to start of field value
When send keys "ALFT,ALFT,ALFT,ALFT,ALFT,ALFT,ALFT,ALFT" to the time machine flux editor
# Delete field value
When send keys "DEL,DEL,DEL,DEL,DEL,DEL,DEL" to the time machine flux editor
# Now check then add variable
When click the time machine script editor variables tab
Then the time machine variable popover is not visible
When hover over the time machine variable "APIVAR"
Then the time machine variable popover is visible
# TODO - No solution as yet to access popover contents - all action make popover disappear
#When hover over the time machine variable "APIVAR"
# When click time machine popover variable dropodown
When click inject the time machine variable "APIVAR"
When click the time machine cell edit submit button
When click dashboard cell save button
Then the dashboard variables button is highlighted
When get the current graph of the cell "Semantic"
When click the value dropdown button for variable "APIVAR"
Then the selected item of the dropdown for variable "APIVAR" is "pulse"
Then the value dropdown for variable "APIVAR" contains
"""
pulse,sema,znacka,hele
"""
When click the item "sema" for variable "APIVAR"
Then the graph of the cell "Semantic" has changed
When get the current graph of the cell "Semantic"
Then the selected item of the dropdown for variable "APIVAR" is "sema"
When click the value dropdown button for variable "APIVAR"
When click the item "hele" for variable "APIVAR"
Then the cell named "Semantic" has no results
When click the value dropdown button for variable "APIVAR"
When click the item "znacka" for variable "APIVAR"
Then the graph of the cell "Semantic" has changed
#When click dashboard cell edit cancel button
#Then the dashboard contains a cell named "Semantic"
#Script with Map variables
@error-collateral
Scenario: Add Map variable to script
Then the dashboard variables button is highlighted
When toggle context menu of dashboard cell named "Semantic"
When click cell content popover configure
#When click the cell edit Script Editor button
# Move to end
When send keys "CTRL+END" to the time machine flux editor
# Send cursor to start of field value
When send keys "ALFT,ALFT,ALFT,ALFT,ALFT,ALFT,ALFT,ALFT,ALFT" to the time machine flux editor
# Delete field value
When send keys "DEL,DEL,DEL,DEL,DEL,DEL,DEL,DEL" to the time machine flux editor
# Now check then add variable
When click the time machine script editor variables tab
Then the time machine variable popover is not visible
When hover over the time machine variable "KARTA"
Then the time machine variable popover is visible
# TODO - No solution as yet to access popover contents - all action make popover disappear
# No solution as yet to access popover contents - all action make popover disappear
#When hover over the time machine variable "KARTA"
#When click time machine popover variable dropodown
When click inject the time machine variable "KARTA"
When click the time machine cell edit submit button
When click dashboard cell save button
Then the dashboard variables button is highlighted
When get the current graph of the cell "Semantic"
When click the value dropdown button for variable "KARTA"
Then the selected item of the dropdown for variable "KARTA" is "poppy"
Then the value dropdown for variable "KARTA" contains
"""
daisy,poppy,salvia,zinnia
"""
When click the item "salvia" for variable "KARTA"
Then the graph of the cell "Semantic" has changed
When get the current graph of the cell "Semantic"
Then the selected item of the dropdown for variable "KARTA" is "salvia"
When click the value dropdown button for variable "KARTA"
When click the item "daisy" for variable "KARTA"
Then the cell named "Semantic" has no results
When click the value dropdown button for variable "KARTA"
When click the item "zinnia" for variable "KARTA"
Then the graph of the cell "Semantic" has changed
#Script with Query variables
@error-collateral
Scenario: Add Query variable to script
Then the dashboard variables button is highlighted
When toggle context menu of dashboard cell named "Semantic"
When click cell content popover configure
#When click the cell edit Script Editor button
# Move to end
When send keys "CTRL+END" to the time machine flux editor
# Send cursor to start of field value
When send keys "ALFT,ALFT,ALFT,ALFT,ALFT,ALFT,ALFT,ALFT,ALFT" to the time machine flux editor
# Delete field value
When send keys "DEL,DEL,DEL,DEL,DEL,DEL,DEL,DEL" to the time machine flux editor
# Now check then add variable
When click the time machine script editor variables tab
Then the time machine variable popover is not visible
When hover over the time machine variable "POKUS"
Then the time machine variable popover is visible
# TODO - No solution as yet to access popover contents - all action make popover disappear
# No solution as yet to access popover contents - all action make popover disappear
#When hover over the time machine variable "POKUS"
#When click time machine popover variable dropodown
When click inject the time machine variable "POKUS"
When click the time machine cell edit submit button
When click dashboard cell save button
Then the dashboard variables button is highlighted
When get the current graph of the cell "Semantic"
When click the value dropdown button for variable "POKUS"
Then the selected item of the dropdown for variable "POKUS" is "pulse"
Then the value dropdown for variable "POKUS" contains
"""
pulse,sema,wombat,znacka
"""
When click the item "sema" for variable "POKUS"
Then the graph of the cell "Semantic" has changed
When get the current graph of the cell "Semantic"
Then the selected item of the dropdown for variable "POKUS" is "sema"
When click the value dropdown button for variable "POKUS"
When click the item "wombat" for variable "POKUS"
Then the cell named "Semantic" has no results
When click the value dropdown button for variable "POKUS"
When click the item "znacka" for variable "POKUS"
Then the graph of the cell "Semantic" has changed
#Change variables in Dashboard view two cells same variable.
@error-collateral
Scenario: Change variables in Dashboard view - two cells
When click the header add cell button
Then the cell edit overlay is loaded as "Name this Cell"
When name dashboard cell "Syntagma"
When click the cell edit Script Editor button
When set the time machine script editor contents to:
"""
from(bucket: "qa")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["_measurement"] == "beat")
|> filter(fn: (r) => r["_field"] == v.POKUS)
|> aggregateWindow(every: v.windowPeriod, fn: mean)
"""
When click the time machine cell edit submit button
When click the cell edit save button
When move the cell named "Syntagma" by "{ "dx": "+400", "dy": "-200" }"
When get the current graph of the cell "Semantic"
When get the current graph of the cell "Syntagma"
When click the value dropdown button for variable "POKUS"
When click the item "pulse" for variable "POKUS"
Then the graph of the cell "Semantic" has changed
Then the graph of the cell "Syntagma" has changed
@error-collateral
Scenario: Change variables in Dashboard view - two variables
When toggle context menu of dashboard cell named "Syntagma"
When click cell content popover configure
# Move to end
When send keys "CTRL+END" to the time machine flux editor
# Send cursor to start of field value
When send keys "AUP,ALFT,ALFT,ALFT,ALFT,ALFT,ALFT,ALFT,ALFT,ALFT" to the time machine flux editor
# Delete field value
When send keys "DEL,DEL,DEL,DEL,DEL,DEL,DEL,DEL" to the time machine flux editor
# Now check then add variable
When click the time machine script editor variables tab
Then the time machine variable popover is not visible
When click inject the time machine variable "KARTA"
When click the time machine cell edit submit button
When click the cell edit save button
When click dashboard time range dropdown
When get the current graph of the cell "Semantic"
When get the current graph of the cell "Syntagma"
When select dashboard Time Range "24h"
Then the graph of the cell "Semantic" has changed
Then the graph of the cell "Syntagma" has changed
When get the current graph of the cell "Semantic"
When get the current graph of the cell "Syntagma"
When click the value dropdown button for variable "KARTA"
When click the item "daisy" for variable "KARTA"
Then the cell named "Syntagma" has no results
Then the graph of the cell "Semantic" is visible
# following check skipped - seems there is a slight change due to refresh
#Then the graph of the cell "Semantic" has not changed
#Dashboard view show/hide variables.
@error-collateral
Scenario: Toggle Variables in Dashboard
Then the value dropdown for variable "POKUS" is visible
Then the dashboard variables button is highlighted
When click the dashboard variables button
Then the dashboard variables button is not highlighted
Then the value dropdown for variable "POKUS" is not visible
When click the dashboard variables button
Then the value dropdown for variable "POKUS" is visible
Then the dashboard variables button is highlighted
#default variable
# NEED TO CLEAN UP
# Variables cached in localstore can influence other tests
@tested
Scenario Outline: Delete Variable
When click nav menu item "Settings"
When click the settings tab "Variables"
#When hover over the "Settings" menu item
#When click nav sub menu "Variables"
When hover over variable card named "<NAME>"
When click delete menu of variable card named "<NAME>"
When click delete confirm of variable card named "<NAME>"
Then the success notification contains "Successfully deleted the variable"
Then the variable card "<NAME>" is not present
When close all notifications
Examples:
|NAME|
|APIVAR|
|KARTA|
|POKUS|

View File

@ -0,0 +1,50 @@
@feature-homePage
@homePage-homePage
Feature: Home Page
Scenario: logout home page
Given I reset the environment
Given run setup over REST "DEFAULT"
When open the signin page
When UI sign in user "DEFAULT"
When open page "HOME" for user "DEFAULT"
When click logout from the home page
Then the sign in page is loaded
When UI sign in user "DEFAULT"
Then the home page is loaded
Scenario: Click Data Collector Panel
When I click the panel "Data Collector"
Then the Telegraf Tab is loaded
@tested
Scenario: Check Dashboards Panel
When hover over the "home" menu item
When click nav menu item "home"
When I click the panel "Dashboard"
Then the Dashboards page is loaded
When click create dashboard control
When click "New Dashboard" in create dashboard dropdown
When name dashboard "Test Dashboard"
When hover over the "home" menu item
When click nav menu item "home"
Then the dashboards panel contains a link to "Test Dashboard"
When click the dashboard link to "Test Dashboard"
Then the dashboard named "Test Dashboard" is loaded
@tested
Scenario: Click Alerting Panel
When hover over the "home" menu item
When click nav menu item "home"
When I click the panel "Alerting"
Then the Alerting page is loaded
Scenario: Logout from Menu
When hover over the "home" menu item
When click nav menu item "home"
When click logout from the home page
Then the sign in page is loaded
When UI sign in user "DEFAULT"

View File

@ -0,0 +1,99 @@
@feature-influx
@influx-influx
Feature: Influx common
Click through the controls common to all influx pages
@tested
Scenario: Open home page
Given I reset the environment
Given run setup over REST "DEFAULT"
When open the signin page
When UI sign in user "DEFAULT"
When open page "HOME" for user "DEFAULT"
Then influx page is loaded
#Then the header contains the org name "DEFAULT"
Then the home page header contains "Getting Started"
@tested
Scenario: Click Data Explorer
#Then the menu item text "Data Explorer" is "hidden"
#When hover over the "explorer" menu item
#Then the menu item text "Data Explorer" is "visible"
When click nav menu item "Explorer"
Then the Data Explorer page is loaded
@tested
Scenario: Click Dashboards
#Then the menu item text "Dashboards" is "hidden"
#When hover over the "dashboards" menu item
#Then the menu item text "Dashboards" is "visible"
When click nav menu item "Dashboards"
Then the Dashboards page is loaded
@tested
Scenario: Click Tasks
#Then the menu item text "Tasks" is "hidden"
#When hover over the "tasks" menu item
#Then the menu item text "Tasks" is "visible"
When click nav menu item "Tasks"
Then the Tasks page is loaded
@tested
Scenario: Click Alerting
#Then the menu item text "Monitoring & Alerting" is "hidden"
#When hover over the "alerting" menu item
#Then the menu item text "Monitoring & Alerting" is "visible"
When click nav menu item "Alerting"
Then the Alerting page is loaded
@tested
Scenario: Click Load Data
#Then the menu item text "Settings" is "hidden"
#When hover over the "loadData" menu item
#Then the menu item text "Load Data" is "visible"
#Then the menu item text "Buckets" is "visible"
#Then the menu item text "Telegraf" is "visible"
#Then the menu item text "Scrapers" is "visible"
When click nav menu item "LoadData"
Then the buckets tab is loaded
Scenario: Click Settings
#Then the menu item text "Settings" is "hidden"
#When hover over the "settings" menu item
#Then the menu item text "Settings" is "visible"
When click nav menu item "Settings"
Then the Settings page is loaded
# Item no longer exists commit=bd91a81123 build_date=2020-04-07T07:57:22Z
#Scenario: Hover Feedback
# Then the menu item text "Feedback" is "hidden"
# When hover over the "feedback" menu item
# Then the menu item text "Feedback" is "visible"
# Then the feedback URL should include "https://docs.google.com/forms"
Scenario: Click home item
#Then the home submenu items are "hidden"
#When hover over the "home" menu item
#Then the home submenu items are "visible"
#When click nav sub menu "Create Organization"
#Then the Create Organization form is loaded
#When open page "HOME" for user "DEFAULT"
#When hover over the "home" menu item
When click nav menu item "home"
Then the home page is loaded
#Then the sign in page is loaded
#When UI sign in user "DEFAULT"
# This appears to have been removed 2020-05-12
# See closed issue #17991
# Scenario: Click Organization
# When click nav menu item "Organization"
# Then the Organization page is loaded
Scenario: Click User
When click nav menu item "User"
Then the user menu items are "visible"
# TODO - create organization from Home Menu Item

View File

@ -0,0 +1,269 @@
@feature-loadData
@loadData-buckets
Feature: Load Data - Buckets
As a user I want to Read Create Update and Delete Buckets
So that I can manage the stores used with Influxdbv2
@tested
Scenario: List Initial Buckets
Given I reset the environment
Given run setup over REST "DEFAULT"
When open the signin page
When UI sign in user "DEFAULT"
When click nav menu item "LoadData"
When click load data tab "Buckets"
Then the buckets tab is loaded
Then the buckets are sorted as "_monitoring,_tasks,DEFAULT"
@tested
Scenario: Exercise Create Bucket Dialog
When click the Create Bucket button
Then the Create Bucket Popup is loaded
Then the Create button of Create Bucket Popup is disabled
Then the Retention Policy radio button "never" is active
Then the Retention Policy intervals controls are not present
When click the Retention Policy "intervals" button
Then the Retention Policy radio button "intervals" is active
Then the Retention Policy radio button "never" is inactive
Then the Retention Policy intervals controls are present
# N.B. controls replaced with dropdown selector 2019-09-11
#Then the Retention Policy warning message contains "Retention period must be at least an hour"
#When enter "61" into the Retention Policy "Seconds" control
#Then the Retention Policy "Seconds" control contains the value "1"
#Then the Retention Policy "Minutes" control contains the value "1"
#Then the Retention Policy warning message contains "Retention period must be at least an hour"
#When clear all Retention Policy interval controls
#When enter "123" into the Retention Policy "Minutes" control
#Then the Retention Policy "Minutes" control contains the value "3"
#Then the Retention Policy "Hours" control contains the value "2"
#Then the Retention Policy warning message has disappeared
#When clear all Retention Policy interval controls
#When enter "80" into the Retention Policy "Hours" control
#Then the Retention Policy "Hours" control contains the value "8"
#Then the Retention Policy "Days" control contains the value "3"
#When clear all Retention Policy interval controls
#When enter "7" into the Retention Policy "Days" control
#Then the Retention Policy "Days" control contains the value "7"
#When clear all Retention Policy interval controls
#When enter "ABCD" into the Retention Policy "Seconds" control
#Then the Retention Policy "Seconds" control contains the value "0"
#When enter "ABCD" into the Retention Policy "Minutes" control
#Then the Retention Policy "Minutes" control contains the value "0"
#When enter "ABCD" into the Retention Policy "Hours" control
#Then the Retention Policy "Hours" control contains the value "0"
#When enter "ABCD" into the Retention Policy "Days" control
#Then the Retention Policy "Days" control contains the value "0"
When input the name of the bucket as "ABCD"
#Then the Retention Policy warning message contains "Retention period must be at least an hour"
When click the Retention Policy "never" button
#Then the Retention Policy warning message has disappeared
Then the Create button of Create Bucket Popup is enabled
When dismiss the Create Bucket Popup
Then the Create Bucket Popup is not present
When click the Create Bucket button
Then the Create Bucket Popup is loaded
Then the Create button of Create Bucket Popup is disabled
When cancel the Create Bucket Popup
Then the Create Bucket Popup is not present
@tested
Scenario Outline: Create Buckets with Retention Policies
When click the Create Bucket button
When input the name of the bucket as "<NAME>"
When set the retention policy of the bucket as "<RETENTION>"
When click the Create Bucket popup Create button
Then the bucket named "<NAME>" is in the list
Then the bucket named "<NAME>" has a Retention Policy of "<RETENTION>"
Examples:
|NAME| RETENTION |
| Trvalá | Never |
| Měsíční | 30d |
| Týdenní | 7d |
| Denní | 24h |
| Půldenní | 12h |
| Hodinová | 1h |
| Oprava | 24h |
@tested
Scenario: Modify Retention Policy
When click on settings for bucket named "Oprava"
Then the Edit Bucket popup is loaded
Then the name edit textbox of the Edit Bucket popup is disabled
Then the form help text contains "To rename bucket use the RENAME button below"
When dismiss the Edit Bucket Popup
Then the Edit Bucket Popup is not present
When click on settings for bucket named "Oprava"
Then the Edit Bucket popup is loaded
When cancel the Edit Bucket Popup
Then the Edit Bucket Popup is not present
When click on settings for bucket named "Oprava"
When set the retention policy of the bucket as "48h"
When click Edit Bucket Popup Save Changes
# N.B. fix following once issue 14905 is resolved
Then the bucket named "Oprava" has a Retention Policy of "48h"
@tested
Scenario: Filter Buckets
When enter "denn" in the Buckets filter field
Then the buckets are sorted as "Denní,Půldenní,Týdenní"
Then the bucket "Oprava" is not in the list
@error-collateral
Scenario: Clear Filter
When clear the Buckets filter field
Then the bucket named "Oprava" is in the list
Then the bucket named "_monitoring" is in the list
Then the bucket named "_tasks" is in the list
Then the bucket named "Týdenní" is in the list
@tested
Scenario: Sort Buckets by Name
When click the sort type dropdown
When click sort by item "Name Desc"
#Given ensure buckets name sort order "desc"
Then the buckets are sorted as "_monitoring,_tasks,Denní,Hodinová,Měsíční,Oprava,Půldenní,DEFAULT,Trvalá,Týdenní"
When click the sort type dropdown
When click sort by item "Name Asc"
# Given ensure buckets name sort order "asc"
Then the buckets are sorted as "Týdenní,Trvalá,DEFAULT,Půldenní,Oprava,Měsíční,Hodinová,Denní,_tasks,_monitoring"
When click the sort type dropdown
When click sort by item "Name Desc"
#Given ensure buckets name sort order "desc"
@error-collateral
Scenario: Sort Buckets by Retention Policy
When click the sort type dropdown
When click sort by item "retentionRules[0].everySeconds-asc"
#When click buckets sort by retention policy
Then the buckets are sorted as "Hodinová,Půldenní,Denní,Oprava,_tasks,Týdenní,_monitoring,Měsíční,Trvalá,DEFAULT"
When click the sort type dropdown
When click sort by item "retentionRules[0].everySeconds-desc"
#When click buckets sort by retention policy
Then the buckets are sorted as "DEFAULT,Trvalá,Měsíční,Týdenní,_monitoring,_tasks,Oprava,Denní,Půldenní,Hodinová"
@tested
Scenario Outline: Delete Buckets
# following check leads to troublesome false positives - todo fix it
# Then the delete button of the card named "<Name>" is not present
When hover over bucket card named "<Name>"
When click the delete button of the card named "<Name>"
When click the confirm delete button of the card named "<Name>"
Then the bucket card named "<Name>" is not in the list
Examples:
| Name |
| Trvalá |
| Měsíční |
| Týdenní |
| Denní |
| Půldenní |
| Hodinová |
| Oprava |
@tested
Scenario: Add Manual Line Protocol Data to Default
Then the add data popover is not present
When click add data button for bucket "DEFAULT"
Then the add data popover for the bucket "DEFAULT" is visible
When click bucket card popover item "Line Protocol"
Then the first page of the Line Protocol Wizard is loaded
When click radio button "Enter Manually"
Then the data point text area is visible
When click the Line Protocol wizard precision dropdown
When click the line Protocol wizard precision "ms"
When enter "12" datapoints with value named "foo" starting at "-2h" with "fibonacci" data of type "int" and prec "ms"
When click the Line Protocol wizard continue button
Then the line Protocol wizard second step opens
Then the Line Protocol wizard step status message is "Data Written Successfully"
When click the Line Protocol wizard finish button
Then the line Protocol wizard is not present
When API sign in user "DEFAULT"
Then the bucket "DEFAULT" for user "DEFAULT" contains:
"""
{ "points": 12, "field": "foo", "measurement": "fibonacci", "start": "-3h", "vals": ["1","233"], "rows": ["1","-1"] }
"""
@error-collateral
Scenario: Add Manual Line Protocol Bad Data to Default
Then the add data popover is not present
When click add data button for bucket "DEFAULT"
Then the add data popover for the bucket "DEFAULT" is visible
When click bucket card popover item "Line Protocol"
Then the first page of the Line Protocol Wizard is loaded
When click radio button "Enter Manually"
Then the data point text area is visible
When enter "bad data" into the line protocol text area
When click the Line Protocol wizard continue button
Then the line Protocol wizard second step opens
Then the Line Protocol wizard step status message contains "Unable to Write Data"
When click the Line Protocol wizard finish button
Then the line Protocol wizard is not present
@tested
Scenario: Add Line Protocol Data from File to Default
When generate a line protocol testdata file "etc/test-data/line-protocol-hydro.txt" based on:
"""
{ "points": 20, "measurement":"level", "start": "-60h", "algo": "hydro", "prec": "sec", "name": "hydro"}
"""
When click add data button for bucket "DEFAULT"
Then the add data popover for the bucket "DEFAULT" is visible
When click bucket card popover item "Line Protocol"
Then the first page of the Line Protocol Wizard is loaded
When click radio button "Upload File"
When add the file "etc/test-data/line-protocol-bogus.txt" to the Line Protocol Wizard file upload
Then the popup wizard import file header contains "line-protocol-bogus.txt"
When click the Line Protocol wizard continue button
Then the popup wizard step state text contains "Unable to Write Data"
Then the popup wizard step is in state "error"
When click the bucket data wizard previous button
When click the Line Protocol wizard precision dropdown
When click the line Protocol wizard precision "ms"
When add the file "etc/test-data/line-protocol-hydro.txt" to the Line Protocol Wizard file upload
Then the popup wizard import file header contains "line-protocol-hydro.txt"
When click the Line Protocol wizard continue button
Then the popup wizard step state text contains "Data Written Successfully"
Then the popup wizard step is in state "success"
When click the Line Protocol wizard finish button
Then the line Protocol wizard is not present
#Then the bucket "DEFAULT" for user "DEFAULT" contains "20" datapoints of "hydro" data with value named "level" starting at "-60h"
Then the bucket "DEFAULT" for user "DEFAULT" contains:
"""
{ "points": 20, "field": "level", "measurement": "hydro", "start": "-60h", "vals": "skip", "rows": ["1","-1"], "name": "hydro" }
"""
@tested
Scenario: Add Scraper to Default
When click add data button for bucket "DEFAULT"
Then the add data popover for the bucket "DEFAULT" is visible
When click bucket card popover item "Scrape Metrics"
When enter the name "Courbet" into the Create Scraper popup name input
When click the create scraper create button
Then the success notification contains "Scraper was created successfully"
When click load data tab "Scrapers"
Then there is a scraper card for "Courbet"
Scenario: Add Telegraf to Default
When click nav menu item "LoadData"
When click load data tab "Buckets"
When click add data button for bucket "DEFAULT"
Then the add data popover for the bucket "DEFAULT" is visible
When click bucket card popover item "Configure Telegraf Agent"
Then the Create Telegraf Config Wizard is loaded
When click the buckets dropdown button
When select the buckets dropdown item "DEFAULT"
When select the telegraf plugin tile "System"
When click the Popup Wizard continue button
When enter the telegraf name "Daumier"
When click the Popup Wizard continue button
Then the success notification contains "telegraf plugin: system."
Then the success notification contains "configurations have been saved"
When close all notifications
When click the Popup Wizard continue button
When click load data tab "Telegrafs"
Then there is a telegraf card for "Daumier"
#TODO - new functionality - click bucket name opens data explorerer

View File

@ -0,0 +1,52 @@
@feature-loadData
@loadData-clientlib
Feature: Load Data - Client Libs
As a user I want to Read Create Update and Delete Client Libraries
So that I can manage the stores used with Influxdbv2
Scenario: Load Initial Client Lib Tab
Given I reset the environment
Given run setup over REST "DEFAULT"
When API sign in user "DEFAULT"
When API create a bucket named "Duchamp" for user "DEFAULT"
When open the signin page
When UI sign in user "DEFAULT"
When click nav menu item "LoadData"
When click load data tab "Client Libraries"
Then the Client Libraries tab is loaded
Scenario: Open C# Popup
When click the "csharp" client library tile
Then the csharp info popup is loaded
Then click copy "Package Manager" to clipboard
Then the success notification contains "has been copied to clipboard"
#Then verify clipboard contains text of "Package Manager"
When close all notifications
Then verify the github repository link contains "influxdb-client-csharp"
Then dismiss the popup
Scenario: Open Go Popup
When click the "go" client library tile
Then the go info popup is loaded
Then verify the github repository link contains "influxdb-client-go"
Then dismiss the popup
Scenario: Open Java Popup
When click the "java" client library tile
Then the java info popup is loaded
Then verify the github repository link contains "influxdb-client-java"
Then dismiss the popup
Scenario: Open Node Popup
When click the "javascript-node" client library tile
Then the node info popup is loaded
Then verify the github repository link contains "influxdb-client-js"
Then dismiss the popup
Scenario: Open Python Popup
When click the "python" client library tile
Then the python info popup is loaded
Then verify the github repository link contains "influxdb-client-python"
Then dismiss the popup
# TODO - check copy to clipboard buttons - N.B. clipboard not available in chromedriver headless

View File

@ -0,0 +1,21 @@
@feature-loadData
@loadData-loadData
Feature: Load Data - Base
As a user I want to open the Load Data page
So that I can explore how data is being loaded into Influxdbv2
@tested
Scenario: Verify Tabs
Given I reset the environment
Given run setup over REST "DEFAULT"
When open the signin page
When UI sign in user "DEFAULT"
When open page "load-data" for user "DEFAULT"
When click load data tab "Buckets"
Then the buckets tab is loaded
When click load data tab "Telegrafs"
Then the Telegraf Tab is loaded
When click load data tab "Scrapers"
Then the Scrapers Tab is loaded
When click load data tab "Tokens"
Then the tokens tab is loaded

View File

@ -0,0 +1,156 @@
@feature-loadData
@loadData-scrapers
Feature: Load Data - Scrapers
As a user I want to Read Create Update and Delete Scrapers
So that I can manage the stores used with Influxdbv2
# Move exercise create scraper popup here
# N.B. can verify scrapers at endpoint http://localhost:9999/api/v2/scrapers
@tested
Scenario: Load Initial Scrapers tab
Given I reset the environment
Given run setup over REST "DEFAULT"
When API sign in user "DEFAULT"
When API create a bucket named "Duchamp" for user "DEFAULT"
When open the signin page
When UI sign in user "DEFAULT"
When click nav menu item "LoadData"
When click load data tab "Scrapers"
Then the scrapers tab is loaded
@tested
Scenario: Exercise create Scraper popup
When click the create scraper button empty
Then the Create Scraper popup is loaded
When dismiss the Create Scraper popup
Then the Create Scraper popup is no longer present
When click the create scraper button from the header
Then the Create Scraper popup is loaded
When cancel the Create Scraper popup
Then the Create Scraper popup is no longer present
When click the create scraper button empty
Then the Create Scraper popup is loaded
When clear the Scraper Popup name input
Then the form element error message is "Name cannot be empty"
Then a form input error icon is shown
Then the Create Scrapper popup create button is disabled
When enter the name "Mumford" into the Create Scraper popup name input
Then the form element error message is not shown
Then no form input error icon is shown
Then the Create Scrapper popup create button is enabled
When click the Create Scrapper buckets dropdown
Then an item for the bucket "DEFAULT" is an item in the buckets dropdown
Then an item for the bucket "Duchamp" is an item in the buckets dropdown
When click the Create Scrapper buckets dropdown
Then NO items in the buckets dropdown are shown
When clear Scraper Popup the Target Url input
Then the form element error message is "Target URL cannot be empty"
Then a form input error icon is shown
Then the Create Scrapper popup create button is disabled
When enter the value "http://localhost:9999/metrics" into the Create Scraper popup url input
Then the form element error message is not shown
Then no form input error icon is shown
Then the Create Scrapper popup create button is enabled
When dismiss the Create Scraper popup
Then the Create Scraper popup is no longer present
@tested
Scenario Outline: Create Scrapers
When click the create scraper button from the header
When clear the Scraper Popup name input
When enter the name "<NAME>" into the Create Scraper popup name input
When click the Create Scrapper buckets dropdown
When select the Scrapper buckets dropdown item "<BUCKET>"
When clear Scraper Popup the Target Url input
When enter the value "<ENDPOINT>" into the Create Scraper popup url input
When click the create scraper create button
Then the success notification contains "Scraper was created successfully"
When close all notifications
Then the create scraper button empty is no longer present
Then there is a scraper card for "<NAME>"
Then the scraper card named "<NAME>" has the bucket "<BUCKET>"
Then the scraper card named "<NAME>" has the endpoint "<ENDPOINT>"
Examples:
| NAME | ENDPOINT | BUCKET |
| Melnik | http://localhost:9999/metrics | DEFAULT |
| Morlaix | http://localhost:9999/metrics | Duchamp |
| Brno | http://localhost:10018/bogus | DEFAULT |
| Brest | http://localhost:10018/bogus | Duchamp |
@error-collateral
Scenario: Filter Scrapers
Then the scraper name sort order is "Brest,Brno,Melnik,Morlaix"
When enter the value "Br" into the scraper filter
Then the scraper name sort order is "Brest,Brno"
Then the scraper card "Melnik" is no longer present in the list
Then the scraper card "Morlaix" is no longer present in the list
When clear the scraper filter
Then the scraper name sort order is "Brest,Brno,Melnik,Morlaix"
@error-collateral
Scenario: Sort Scrapers by Name
When click the sort type dropdown
When click sort by item "Name Desc"
#When click the scraper sort by name button
Then the scraper name sort order is "Morlaix,Melnik,Brno,Brest"
When click the sort type dropdown
When click sort by item "Name Asc"
#When click the scraper sort by name button
Then the scraper name sort order is "Brest,Brno,Melnik,Morlaix"
@error-collateral
Scenario: Sort Scrapers by URL
When click the sort type dropdown
When click sort by item "URL Asc"
#When click the scraper sort By URL button
Then the scraper name sort order is "Brno,Brest,Melnik,Morlaix"
When click the sort type dropdown
When click sort by item "URL Desc"
#When click the scraper sort By URL button
Then the scraper name sort order is "Melnik,Morlaix,Brno,Brest"
@error-collateral
Scenario: Sort Scrapers by Bucket
When click the sort type dropdown
When click sort by item "Bucket Asc"
#When click the scraper sort By Bucket button
Then the scraper name sort order is "Morlaix,Brest,Melnik,Brno"
When click the sort type dropdown
When click sort by item "Bucket Desc"
#When click the scraper sort By Bucket button
Then the scraper name sort order is "Melnik,Brno,Morlaix,Brest"
@error-collateral
Scenario: Rename Scraper
When hover over the scraper card name "Brno"
When click the scraper card name edit control for the card "Brno"
When Enter the value "Plzeň" for the card "Brno"
Then the success notification contains "Scraper "Plzeň" was updated successfully"
Then there is a scraper card for "Plzeň"
Then the scraper card "Brno" is no longer present in the list
@error-collateral
Scenario Outline: Verify Scraper data
Then the named query "<NAMED_QUERY>" by user "<USER>" on the bucket "<BUCKET>" contains the values "<EXPECTED_VALUES>"
Examples:
|USER|BUCKET|NAMED_QUERY|EXPECTED_VALUES|
|DEFAULT| Duchamp | Measurements | boltdb_reads_total,go_info,go_threads,influxdb_info,storage_reads_seeks |
|DEFAULT| DEFAULT | Measurements | boltdb_reads_total,go_info,go_threads,influxdb_info,storage_reads_seeks |
@tested
Scenario Outline: Delete Scraper
Then the delete button of the scraper card named "<NAME>" is not present
When hover over scraper card named "<NAME>"
When click the delete button of the scraper card named "<NAME>"
When click the confirm delete button of the scraper card named "<NAME>"
Then the scraper card "<NAME>" is no longer present in the list
Examples:
| NAME |
| Melnik |
| Morlaix |
| Plzeň |
| Brest |

View File

@ -0,0 +1,226 @@
@feature-loadData
@loadData-telegrafs
Feature: Load Data - Telegrafs
Scenario: Load Initial Telegraf tab
Given I reset the environment
Given run setup over REST "DEFAULT"
When API sign in user "DEFAULT"
When API create a bucket named "Duchamp" for user "DEFAULT"
When API create a label "Cesko" described as "Pravda vitezi" with color "#AAFFAA" for user "DEFAULT"
When API create a label "Mesto" described as "Matka mest" with color "#FFAAAA" for user "DEFAULT"
When open the signin page
When UI sign in user "DEFAULT"
When click nav menu item "LoadData"
When click load data tab "Telegrafs"
Then the telegrafs tab is loaded
Scenario: Exercise create Telegraf wizard
When click the create Telegraf button empty
Then the Create Telegraf Wizard is loaded
When dismiss the Create Telegraf Wizard
Then the Create Telegraf Wizard is no longer present
When click the create Telegraf button in header
When click the select bucket dropdown in the Create Telegraf Wizard
When click the bucket item "DEFAULT" in the Create Telegraf Wizard
When enter the value "NGI" in create Telegraf Wizard filter plugins
Then the Create Telegraf wizard plugin tile "NGINX" is visible
Then the Create Telegraf wizard plugin tile "System" is not present
Then the Create Telegraf wizard plugin tile "Redis" is not present
Then the Create Telegraf wizard plugin tile "Docker" is not present
When clear the create Telegraf Wizard plugin filter
When click the plugin tile "System" in the Create Telegraf Wizard
When click the Popup Wizard continue button
Then the create Telegraf Wizard second step is loaded
Then the create Telegraf plugins sidebar contains "cpu,disk,diskio,mem,net,processes,swap,system"
Then the create Telegraf plugin sidebar "system" item is in state "success"
Then the create Telegraf plugin sidebar "cpu" item is in state "success"
Then the create Telegraf plugin sidebar "mem" item is in state "success"
When dismiss the Create Telegraf Wizard
Then the Create Telegraf Wizard is no longer present
When click the create Telegraf button empty
Scenario Outline: Edit Plugin Values
When click the plugin tile "<PLUGIN>" in the Create Telegraf Wizard
When click the Popup Wizard continue button
Then the create Telegraf Wizard second step is loaded
Then the create Telegraf plugins sidebar contains "<PLUGIN>"
Then the create Telegraf plugin sidebar "<PLUGIN>" item is in state "neutral"
When click the create Telegraf plugin sidebar "<PLUGIN>" item
Then the create Telegraf edit plugin "<PLUGIN>" step is loaded
# When click the Popup Wizard done button
# Then the create Telegraf plugin sidebar "<PLUGIN>" item is in state "error"
# When click the create Telegraf plugin sidebar "<PLUGIN>" item
When enter the values <FAKE_VALUES> into the fields <FIELDS>
Then verify the edit plugin error notification with message "<ERRMSGS>"
When clear the create Telegraf edit plugin fields <FIELDS>
When enter the values <TRUE_VALUES> into the fields <FIELDS>
When click the Popup Wizard done button
Then the create Telegraf plugin sidebar "<PLUGIN>" item is in state "success"
When click the wizard previous button
Then the Create Telegraf Wizard is loaded
Then the Create Telegraf wizard plugin tile "<PLUGIN>" is selected
When click the plugin tile "<PLUGIN>" in the Create Telegraf Wizard
Then the Create Telegraf wizard plugin tile "<PLUGIN>" is not selected
Then the popup wizard continue button is disabled
Examples:
| PLUGIN | FAKE_VALUES | FIELDS | ERRMSGS | TRUE_VALUES |
| Docker | SKIP | endpoint | SKIP | http://localhost:10080 |
| Kubernetes | ASDF | url | Must be a valid URI. | http://localhost:10080 |
| Redis | SKIP,SKIP | servers,password | SKIP | tcp://localhost:6379,wumpus |
Scenario: Edit NGINX Plugin Values
When click the plugin tile "NGINX" in the Create Telegraf Wizard
When click the Popup Wizard continue button
Then the create Telegraf Wizard second step is loaded
Then the create Telegraf plugins sidebar contains "NGINX"
Then the create Telegraf plugin sidebar "NGINX" item is in state "neutral"
When click the create Telegraf plugin sidebar "NGINX" item
Then the create Telegraf edit plugin "NGINX" step is loaded
When enter the values ASDF into the fields urls
Then verify the edit plugin error notification with message "NONE"
When clear the create Telegraf edit plugin fields urls
When enter the values http://localhost:10080 into the fields urls
When click the NGINX configuration add button
Then the NGINX configuration URLs list contains "1" items
When click delete for the first NGINX configuration URL
When click confirm delete of NGINX configuration URL
Then the NGINX configuration URLs list is empty
When click the Popup Wizard done button
Then the create Telegraf plugin sidebar "NGINX" item is in state "error"
When click the create Telegraf plugin sidebar "NGINX" item
When enter the values http://localhost:10080 into the fields urls
When click the NGINX configuration add button
When click the Popup Wizard done button
Then the create Telegraf plugin sidebar "NGINX" item is in state "success"
When click the wizard previous button
Then the Create Telegraf Wizard is loaded
Then the Create Telegraf wizard plugin tile "NGINX" is selected
When click the plugin tile "NGINX" in the Create Telegraf Wizard
Then the Create Telegraf wizard plugin tile "NGINX" is not selected
Then the popup wizard continue button is disabled
Scenario: Cleanup from Edit Plugin Values
When dismiss the Create Telegraf Wizard
#N.B. just add UI artifacts - no need to check backend at this point
Scenario Outline: Create Telegraf
When click the create Telegraf button in header
When click the select bucket dropdown in the Create Telegraf Wizard
When click the bucket item "<BUCKET>" in the Create Telegraf Wizard
When click the plugin tile "<PLUGIN>" in the Create Telegraf Wizard
When click the Popup Wizard continue button
When enter the name "<NAME>" in the Create Telegraf Wizard
When enter the description "<DESCR>" in the Create Telegraf Wizard
When click the Popup Wizard continue button
Then the success notification contains "<SUCCESS_MSG>"
Then the success notification contains "Your configurations have been saved"
When close all notifications
Then the create Telegraf Wizard final step is loaded
When click the Create Telegraf Wizard finish button
Then there is a telegraf card for "<NAME>"
Then the bucket of the telegraf card "<NAME>" is "<BUCKET>"
Examples:
| PLUGIN | BUCKET | NAME | DESCR | SUCCESS_MSG |
| System | DEFAULT |Strakonice | Lorem ipsum | Successfully created dashboards for telegraf plugin: system. |
| Docker | Duchamp |Decin | Lorem ipsum | SKIP |
| Kubernetes | DEFAULT |Kladno | Lorem ipsum | SKIP |
| NGINX | Duchamp |Nymburk | Lorem ipsum | SKIP |
| Redis | DEFAULT |Rakovnik | Lorem ipsum | SKIP |
Scenario: Sort Telegrafs by Name
Then the telegraf sort order is "Decin,Kladno,Nymburk,Rakovnik,Strakonice"
When click the sort type dropdown
When click sort by item "Name Desc"
#When click the telegraf sort by name button
Then the telegraf sort order is "Strakonice,Rakovnik,Nymburk,Kladno,Decin"
When click the sort type dropdown
When click sort by item "Name Asc"
#When click the telegraf sort by name button
Then the telegraf sort order is "Decin,Kladno,Nymburk,Rakovnik,Strakonice"
Scenario: Filter Telegrafs
When enter the value "Rak" into the Telegrafs filter
Then the telegraf sort order is "Rakovnik,Strakonice"
Then the telegraf cards "Decin,Kldano,Nymburk" are no longer present
When clear the Telegrafs filter
Then the telegraf sort order is "Decin,Kladno,Nymburk,Rakovnik,Strakonice"
Scenario: Verify setup instructions
When click on setup instructions for the telegraf card "Decin"
Then the telegraf setup instruction popup is loaded
When dismiss the popup
Then popup is not loaded
Scenario: Verify configuration
When click on the name of the telegraf card "Nymburk"
Then the telegraf configuration popup for "Nymburk" is loaded
When dismiss the popup
Then popup is not loaded
Scenario: Edit Telegraf Card
When hover over the name of the telegraf card "Nymburk"
When click the name edit icon of the telegraf card "Nymburk"
When clear the name input of the telegraf card "Nymburk"
When set the name input of the telegraf card "Nymburk" to "Norimberk"
Then the Telegraf Card "Nymburk" can no longer be found
Then there is a telegraf card for "Norimberk"
When hover over the description of the telegraf Card "Norimberk"
When click the description edit icon of the telegraf card "Norimberk"
When clear the desrciption input of the telegraf card "Norimberk"
When set the description input of the telegraf card "Norimberk" to "Hunt the Wumpus"
Then the description of the telegraf card "Norimberk" is "Hunt the Wumpus"
Scenario: Add labels to telegraf
Then the Label Popup for the Telegraf Card "Kladno" is not present
When click Add Label for Telegraf Card "Kladno"
Then the Label Popup for the Telegraf Card "Kladno" is visible
Then the item "Cesko" is in the Telegraf Card "Kladno" label select list
Then the item "Mesto" is in the Telegraf Card "Kladno" label select list
When filter the Telegraf Card "Kladno" label list with "Ce"
Then the item "Cesko" is in the Telegraf Card "Kladno" label select list
Then the item "Mesto" is NOT in the Telegraf Card "Kladno" label select list
When clear the label filter of the Telegraf Card "Kladno"
Then the item "Mesto" is in the Telegraf Card "Kladno" label select list
When click the item "Cesko" is in the Telegraf Card "Kladno" label select list
Then there is a label pill "Cesko" for the Telegraf Card "Kladno"
Then the item "Cesko" is NOT in the Telegraf Card "Kladno" label select list
When click the item "Mesto" is in the Telegraf Card "Kladno" label select list
Then there is a label pill "Mesto" for the Telegraf Card "Kladno"
Then the item "Mesto" is NOT in the Telegraf Card "Kladno" label select list
Then the label select list for "Kladno" shows the empty state message
When enter the value "Lidstvo" into the Telegraf Card "Kladno" label filter
Then the create Label popup is loaded
When dismiss the popup
Then popup is not loaded
Scenario: Delete label from telegraf
When hover over the label pill "Cesko" for the Telegraf Card "Kladno"
When click delete the label pill "Cesko" for the Telegraf Card "Kladno"
Then the label pill "Cesko" for the Telegraf Card "Kladno" is NOT present
When click Add Label for Telegraf Card "Kladno"
# Affected by issue 16528
Then the item "Cesko" is in the Telegraf Card "Kladno" label select list
# lose focus
When click telegraf card "Kladno"
Then the Label Popup for the Telegraf Card "Kladno" is not present
Scenario Outline: Delete Telegraf Card
When hover over telegraf card "<NAME>"
When click delete for telegraf card "<NAME>"
When click delete confirm for telegraf card "<NAME>"
Then the Telegraf Card "<NAME>" can no longer be found
Examples:
|NAME|
|Decin|
|Strakonice|
|Kladno |
|Norimberk |
|Rakovnik |
# N.B. can verify telegrafs at endpoint http://localhost:9999/api/v2/telegrafs
# TODO - Test installation of telegraf and instructions - check back end

View File

@ -0,0 +1,181 @@
@feature-loadData
@loadData-tokens
Feature: Load Data - Tokens
@tested
Scenario: Load Initial Tokens tab
Given I reset the environment
Given run setup over REST "DEFAULT"
When API sign in user "DEFAULT"
When API create a bucket named "Duchamp" for user "DEFAULT"
When API create a bucket named "Courbet" for user "DEFAULT"
When API create a bucket named "Corot" for user "DEFAULT"
When open the signin page
When UI sign in user "DEFAULT"
When click nav menu item "LoadData"
When click load data tab "Tokens"
Then the tokens tab is loaded
Then the tokens list contains the token described as "admin's Token"
@tested
Scenario: Exercise Create Read/Write Token Popup
When click the generate token dropdown
When click the generate token item "read-write"
Then the generate read-write token popup is loaded
When click the "Read" radio button "All Buckets"
Then the "Read" panel shows the empty state text
Then the bucket selector for the "Read" panel is not present
When click the "Write" radio button "All Buckets"
Then the "Write" panel shows the empty state text
Then the bucket selector for the "Write" panel is not present
When click the "Read" radio button "Scoped"
Then the "Read" panel empty state text is not present
Then the "Read" panel bucket selector is present
When click the "Write" radio button "Scoped"
Then the "Write" panel empty state text is not present
Then the "Write" panel bucket selector is present
Then the "Read" panel bucket list contains "DEFAULT,Corot,Courbet,Duchamp"
Then the "Write" panel bucket list contains "DEFAULT,Corot,Courbet,Duchamp"
When filter the "Read" panel bucket selector with "Co"
Then the "Read" panel bucket list contains "Corot,Courbet"
Then the "Read" panel bucket list does not contain "DEFAULT,Duchamp"
When clear the "Read" panel bucket selector
Then the "Read" panel bucket list contains "DEFAULT,Corot,Courbet,Duchamp"
When filter the "Write" panel bucket selector with "Co"
Then the "Write" panel bucket list contains "Corot,Courbet"
Then the "Write" panel bucket list does not contain "DEFAULT,Duchamp"
When clear the "Write" panel bucket selector
Then the "Write" panel bucket list contains "DEFAULT,Corot,Courbet,Duchamp"
When click "Read" panel select all buckets
Then the "Read" panel buckets "DEFAULT,Corot,Courbet,Duchamp" are selected
When click "Read" panel deselect all buckets
Then the "Read" panel buckets "DEFAULT,Corot,Courbet,Duchamp" are not selected
When click the "Read" panel bucket "Courbet"
Then the "Read" panel buckets "Courbet" are selected
Then the "Read" panel buckets "DEFAULT,Corot,Duchamp" are not selected
When click the "Read" panel bucket "Courbet"
Then the "Read" panel buckets "DEFAULT,Corot,Courbet,Duchamp" are not selected
When click "Write" panel select all buckets
Then the "Write" panel buckets "DEFAULT,Corot,Courbet,Duchamp" are selected
When click "Write" panel deselect all buckets
Then the "Write" panel buckets "DEFAULT,Corot,Courbet,Duchamp" are not selected
When click the "Write" panel bucket "Courbet"
Then the "Write" panel buckets "Courbet" are selected
Then the "Write" panel buckets "DEFAULT,Corot,Duchamp" are not selected
When click the "Write" panel bucket "Courbet"
Then the "Write" panel buckets "DEFAULT,Corot,Courbet,Duchamp" are not selected
When dismiss the popup
Then popup is not loaded
When click the generate token dropdown
When click the generate token item "read-write"
Then the generate read-write token popup is loaded
When click popup cancel button
Then popup is not loaded
@error-collateral
Scenario: Exercise Create All Access Token Popup
When click the generate token dropdown
When click the generate token item "all-access"
Then the generate all-access token popup is loaded
When dismiss the popup
Then popup is not loaded
When click the generate token dropdown
When click the generate token item "all-access"
When click all-access token popup cancel
Then popup is not loaded
@tested
Scenario Outline: Create Token
When click the generate token dropdown
When select token type based on <PRIVILEGES> type
When set token description for <PRIVILEGES> as <DESCR>
When set token privileges for <BUCKET> as <PRIVILEGES>
When click popup save based on <PRIVILEGES>
Then the success notification contains "Token was created successfully"
When close all notifications
Then the tokens list contains the token described as "<DESCR>"
Examples:
|DESCR|BUCKET|PRIVILEGES|
| Un enterrement a Ornans | Courbet | RW |
| Nu descendant un escalier | Duchamp | R |
| La Femme a la perle | Corot | RW |
| Dismaland | DEFAULT | R |
| Cambpells Soup | All | RW |
| La Jocande | ALL |ALL |
@tested
Scenario Outline: Disable Token
When disable the token described as <DESCR>
Then the token described as <DESCR> is disabled
Examples:
|DESCR|
| Dismaland |
| Nu descendant un escalier |
| Cambpells Soup |
# Scenario: Sort By Status # not working see issue 15301
@error-collateral
Scenario: Sort By Name
Then the first tokens are sorted by description as "admin's Token, Campbells Soup, Dismaland, La Femme a la perle, La Jocande"
When click the tokens sort By Name button
Then the first tokens are sorted by description as "Un enterrement a Ornans, Nu descendant un escalier, La Jocande, La Femme a la perle, Dismaland"
When click the tokens sort By Name button
Then the first tokens are sorted by description as "admin's Token, Campbells Soup, Dismaland, La Femme a la perle, La Jocande"
@error-collateral
Scenario: Edit Description
When hover over the token description "La Jocande"
When click the token description toggle for "La Jocande"
When clear the edit input for description "La Jocande"
When set the new description of "La Jocande" to "La Dame a l hermine"
Then the success notification contains "Token was updated successfully"
Then the tokens list contains the token described as "La Dame a l hermine"
Then the tokens list does not contain the token described as "La Jocande"
When close all notifications
@tested
Scenario: Enable Token
When enable the token described as "Nu descendant un escalier"
Then the token described as "Nu descendant un escalier" is enabled
Then the success notification contains "Token was updated successfully"
When close all notifications
@tested
Scenario Outline: Review Token
When click on the token described as "<DESCR>"
Then the review token popup is loaded
Then the review token popup matches "<BUCKETS>" and "<PRIVILEGES>"
When dismiss the popup
Then popup is not loaded
Examples:
|DESCR|BUCKETS|PRIVILEGES|
| Un enterrement a Ornans | Courbet | read,write |
| Nu descendant un escalier | Duchamp | read |
| Cambpells Soup | All | read,write |
| La Dame a l hermine | ALL |ALL |
@tested
Scenario Outline: Delete Token
When hover over token card described as "<DESCR>"
When click the delete button of the token card described as "<DESCR>"
#When click delete confirm of the token card described as "<DESCR>"
When click token card popover delete confirm
Then the success notification contains "Token was deleted successfully"
Then the tokens list does not contain the token described as "<DESCR>"
Then close all notifications
Examples:
|DESCR|
| Un enterrement a Ornans |
| Nu descendant un escalier |
| La Femme a la perle |
| Dismaland |
| Cambpells Soup |
| La Dame a l hermine |

View File

@ -0,0 +1,524 @@
@feature-monitoring
@monitoring-alerts
@use-live-data
Feature: Monitoring - Alerts - Base
As a user I want to setup alerts
So that I can be notified of important changes in the data
@tested
Scenario: Load Initial Alerts view
Given I reset the environment
Given run setup over REST "DEFAULT"
#When hover over the "alerting" menu item
#When click nav sub menu "Monitoring & Alerting"
When API sign in user "DEFAULT"
When API create a label "Peano" described as "Theorie des ensembles" with color "#AAFFAA" for user "DEFAULT"
When API create a label "Euclide" described as "Geometrie euclidienne" with color "#FFAAAA" for user "DEFAULT"
When API create a label "Leibniz" described as "Calcul infinitésimal" with color "#AAAAFF" for user "DEFAULT"
When API create a label "Descartes" described as "Géométrie analytique" with color "#FFFFAA" for user "DEFAULT"
When start live data generator
# It seems 5s is the quickest we can use stably given default values in create check controls
# Tried 1s, but need to use agg function like mean so the checks do not seem to match
"""
{ "pulse": 5000, "model": "count10" }
"""
When open the signin page
When UI sign in user "DEFAULT"
When click nav menu item "Alerting"
Then the Alerting page is loaded
When wait "10" seconds
@tested
Scenario: Exercise Initial Alerts view Controls
Then the notification rules create dropdown is disabled
When click alerting tab "checks"
When click the create check button
Then the create check dropodown list contains the items
"""
threshold,deadman
"""
When click the create check button
Then the create check dropdown list is not visible
When hover the create check question mark
Then the create check tooltip is visible
When hover the alerts page title
Then the create check tooltip is not visible
When click alerting tab "endpoints"
When hover the create endpoint question mark
Then the create endpoint tooltip is visible
When hover the alerts page title
Then the create endpoint tooltip is not visible
When click alerting tab "rules"
When hover the create rule question mark
Then the create rules tooltip is visible
When hover the alerts page title
Then the create rules tooltip is not visible
When click alerting tab "endpoints"
When click create endpoint button
Then the create endpoint popup is loaded
When dismiss the popup
Then popup is not loaded
When click alerting tab "checks"
When click the first time create threshold check
Then the edit check overlay is loaded
When dismiss edit check overlay
Then the edit check overlay is not loaded
When click the first time create deadman check
Then the edit check overlay is loaded
When dismiss edit check overlay
Then the edit check overlay is not loaded
# Create and start endpoint listener for notification checks - maybe move to separate endpoints test suite
# Exercise Configure Check -- N.B. try and reuse dashboard time machine for Define Query
# TODO - Check illogical alert thresholds
# TODO - add simple tags check
@tested
Scenario: Exercise Configure Check - Threshold
When click the create check button
When click the create check dropdown item "Threshold"
# Query Builder steps cover same library as in dashboards - TODO - check for gaps
# For now cover just configure check step
When click check editor configure check button
Then the configure check view is loaded
Then the create check checklist contains:
"""
[{ "state": "error", "text": "One field" },
{ "state": "valid", "text": "One aggregate function" },
{ "state": "error", "text": "One or more thresholds"}]
"""
Then the check interval hint dropdown list is not visible
When click on check interval input
Then the check interval hint dropdown list includes
"""
5s,15s,1m,6h,24h,30d
"""
When click the interval hint dropdown list item "5m"
Then the check interval hint dropdown list is not visible
Then the interval indicator is set to "5m"
Then the check offset hint dropdown list is not visible
When click the check offset interval input
Then the check offset hint dropdown list includes
"""
0s,5s,1m,1h,12h,2d
"""
When click the offset hint dropdown list item "1m"
Then the check offset hint dropdown list is not visible
Then the offset input is set to "1m"
When update the check message template to
"""
Kapela z Varsavy
"""
Then the check message tempate contains
"""
Kapela z Varsavy
"""
When click add threshold condition "CRIT"
When click the threshold definition dropdown for condition "CRIT"
# TODO - after issue 17729 is resolved - should be equal criteria e.g. n == 0
Then the threshold definition dropdown for "CRIT" contain items:
"""
is above,is below,is inside range,is outside range
"""
When click the threshold definition dropodown item "Is Inside Range" for condition "CRIT"
Then there is a binary boundary for the threshold "CRIT" with values "20" and "100"
# N.B. currently cannot easily set negatve values - TODO use negative values once #17782 is resolved
# N.B. TODO - check dimensions of inputs - currently in smaller views they are unreadable #17783
When set the binary boundary for the threshold "CRIT" from "0" to "1000"
Then there is a binary boundary for the threshold "CRIT" with values "0" and "1000"
When click add threshold condition "WARN"
When click the threshold definition dropdown for condition "WARN"
When click the threshold definition dropodown item "Is Below" for condition "WARN"
When set the unary boundary value for the threshold definition "WARN" to "0"
Then there is a unary boundary for the threshhold "WARN" with the value "0"
When dismiss edit check overlay
Then the first time create threshold check is visible
Then the first time create deadman check is visible
@error-collateral
Scenario: Exercise configure check Deadman
# Just check Deadman fields others were covered in threshold test
When click the create check button
When click the create check dropdown item "Deadman"
When click check editor configure check button
Then the create check checklist contains:
"""
[{ "state": "error", "text": "One field" }]
"""
When click the deadman definition No Values For input
Then the deadman definition hints dropdown contains:
"""
15s,5m,1h,12h,7d
"""
When click the deadman definition hint dropdown item "1m"
Then the deadman definition No Values For input contains "1m"
When set the value of the deadman definition No Values for input to "30m"
When click the deadman definition level dropdown
Then the deadman definition level dropdown contains:
"""
CRIT,WARN,INFO,OK
"""
When click the deadman definition level dropdown item "WARN"
Then the deadman definition level dropdown selected item is "WARN"
When click the deadman definition Stop Checking input
Then the deadman definition stop hints dropdown contains:
"""
5s,1m,1h,24h,7d,30d
"""
When click the deadman definition stop hint dropdown item "5m"
Then the deadman definition stop input contains "5m"
When set the value of the definition stop input to "10m"
Then the deadman definition stop input contains "10m"
When dismiss edit check overlay
Then the first time create threshold check is visible
Then the first time create deadman check is visible
# Create Threshold Alerts
@tested
Scenario: Create Simple Threshold Check
When click the first time create threshold check
Then the create check checklist contains:
"""
[{ "state": "error", "text": "One field" },
{ "state": "valid", "text": "One aggregate function" },
{ "state": "error", "text": "One or more thresholds"}]
"""
Then the save check button is disabled
When enter the alert check name "Simple Count Check"
When send keys "ENTER"
When click the tag "test" in builder card "1"
When click the tag "val" in builder card "2"
When click the query builder function "mean"
Then the create check checklist contains:
"""
[{ "state": "valid", "text": "One field" },
{ "state": "valid", "text": "One aggregate function" },
{ "state": "error", "text": "One or more thresholds"}]
"""
Then the save check button is disabled
When click the time machine query builder function duration input
When click the query builder function duration suggestion "5s"
When click the time machine cell edit submit button
Then the time machine cell edit preview graph is shown
When click check editor configure check button
Then the interval indicator is set to "5s"
Then the time machine cell edit preview graph is shown
When enter into interval offset "1s"
When send keys "ENTER"
When update the check message template to
"""
${ r._check_name } is: ${ r._level } value was ${string(v: r.val)}
"""
When click add threshold condition "CRIT"
When click the threshold definition dropdown for condition "CRIT"
When click the threshold definition dropodown item "Is Above" for condition "CRIT"
When set the unary boundary value for the threshold definition "CRIT" to "7.5"
Then the create check checklist is not present
Then the save check button is enabled
Then the time machine cell edit preview contains threshold markers:
"""
CRIT
"""
When click the check editor save button
Then there is an alert card named "Simple Count Check"
# Create Deadman Alerts
@error-collateral
Scenario: Create simple Critical Deadman Check
# Just check Deadman fields others were covered in threshold test
When click the create check button
When click the create check dropdown item "Deadman"
When enter the alert check name "Deadman Critical Check"
When click the tag "test" in builder card "1"
When click the tag "val" in builder card "2"
When click the time machine cell edit submit button
Then the time machine cell edit preview graph is shown
When click check editor configure check button
When set the check interval input to "10s"
When set the check offset interval input "2s"
When click the edit check add tag button
When set the check tag key of tag "1" to "mrtvola"
When set the check tag value of tag "1" to "neboztik"
When click the edit check add tag button
When set the check tag key of tag "2" to "kartoffel"
When set the check tag value of tag "2" to "brambor"
When update the check message template to
"""
${ r._check_name } is: ${ r._level } value [${string(v: r.val)}] has stopped reporting
"""
When set the value of the deadman definition No Values for input to "30s"
When set the value of the definition stop input to "2m"
When click the check editor save button
Then there is an alert card named "Deadman Critical Check"
# Need second card for filter and sort tests
@error-collateral
Scenario: Create simple Warn Deadman Check
# Just check Deadman fields others were covered in threshold test
When click the create check button
When click the create check dropdown item "Deadman"
When enter the alert check name "Deadman Warn Check"
When click the tag "test" in builder card "1"
When click the tag "val" in builder card "2"
When click the time machine cell edit submit button
Then the time machine cell edit preview graph is shown
When click check editor configure check button
When set the check interval input to "10s"
When set the check offset interval input "2s"
When click the edit check add tag button
When update the check message template to
"""
${ r._check_name } is: ${ r._level } has stopped reporting. Last value [${string(v: r.val)}]
"""
When set the value of the deadman definition No Values for input to "20s"
When set the value of the definition stop input to "1m"
When click the check editor save button
Then the error notification contains "Failed to create check: tag must contain a key and a value"
When close all notifications
When remove check tag key "1"
When click the check editor save button
Then there is an alert card named "Deadman Warn Check"
# TODO - EDIT Threshold Check and drag threshold control in graph
# Edit Check Card
@error-collateral
Scenario: Edit Check Card
When hover over the name of the check card "Deadman Warn Check"
When click the name edit button of the check card "Deadman Warn Check"
When update the active check card name input to "Veille automatique - Avertissement"
When send keys "ENTER"
Then there is an alert card named "Veille automatique - Avertissement"
When hover over the description of the check card "Veille automatique - Avertissement"
When click the description edit button of the check card "Veille automatique - Avertissement"
When update the active check card description input to:
"""
Que ta voix, chat mystérieux, Chat séraphique, chat étrange... Baudelaire
"""
When send keys "ENTER"
Then the check card "Veille automatique - Avertissement" contains the description:
"""
Que ta voix, chat mystérieux, Chat séraphique, chat étrange... Baudelaire
"""
# Add labels to checks
@tested
Scenario: Add Labels To Checks
When click empty label for check card "Deadman Critical Check"
Then the add label popover is present
# dismiss popover
# TODO - once #17853 is fixed - use ESC key to dismiss popover
When click the checks filter input
Then the add label popover is not present
When click the add labels button for check card "Deadman Critical Check"
Then the add label popover is present
Then the add label popover contains the labels
"""
Peano,Euclide,Leibniz,Descartes
"""
When click the label popover item "Peano"
When click the label popover item "Leibniz"
Then the add label popover contains the labels
"""
Euclide,Descartes
"""
Then the add label popover does not contain the labels:
"""
Peano,Leibniz
"""
When set the label popover filter field to "Godel"
Then the add label popover does not contain the labels:
"""
Euclide,Descartes
"""
Then the label popover contains create new "Godel"
When clear the popover label selector filter
Then the add label popover contains the labels
"""
Euclide,Descartes
"""
Then the add label popover does not contain the labels:
"""
Peano,Leibniz
"""
Then the add label popover does not contain create new
# TODO - use escape to close popover once #17853 is resolved
When click the checks filter input
Then the add label popover is not present
Then the check card "Deadman Critical Check" contains the label pills:
"""
Peano,Leibniz
"""
When remove the label pill "Peano" from the check card "Deadman Critical Check"
Then the check card "Deadman Critical Check" contains the label pills:
"""
Leibniz
"""
Then the check card "Deadman Critical Check" does not contain the label pills:
"""
Peano
"""
When click the add labels button for check card "Deadman Critical Check"
Then the add label popover contains the labels
"""
Peano,Euclide,Descartes
"""
# TODO - use escape to close popover once #17853 is resolved
When click the checks filter input
# Clone check
@error-collateral
Scenario: Clone Check
When hover over the name of the check card "Simple Count Check"
# When wait "1" seconds
When click the check card "Simple Count Check" clone button
When click the check card "Simple Count Check" clone confirm button
Then there is an alert card named "Simple Count Check (clone 1)"
When click the check card name "Simple Count Check (clone 1)"
Then the edit check overlay is loaded
Then the current edit check name is "Simple Count Check (clone 1)"
Then the interval indicator is set to "5s"
Then the offset input is set to "1s"
Then the check message tempate contains
"""
${ r._check_name } is: ${ r._level } value was ${string(v: r.val)}
"""
Then there is a unary boundary for the threshhold "CRIT" with the value "7.5"
When click checkeditor define query button
Then there are "3" time machine builder cards
Then time machine builder card "1" contains:
"""
test
"""
Then time machine builder card "2" contains:
"""
val
"""
Then the item "test" in builder card "1" is selected
Then the item "val" in builder card "2" is selected
# TODO - verify Bucket Card contents after #17879 fixed
When enter the alert check name "Bécik"
When click the check editor save button
Then there is an alert card named "Bécik"
# Filter Checks
@error-collateral
Scenario: Filter Checks
Then the check cards column contains
"""
Simple Count Check, Deadman Critical Check, Veille automatique - Avertissement, Bécik
"""
When enter into the check cards filter field "Check"
Then the check cards column contains
"""
Simple Count Check, Deadman Critical Check
"""
Then the check cards column does not contain
"""
Veille automatique - Avertissement, Bécik
"""
When enter into the check cards filter field "Be"
Then the "checks" cards column empty state message is "No checks match your search"
When enter into the check cards filter field "Bé"
Then the check cards column contains
"""
Bécik
"""
Then the check cards column does not contain
"""
Simple Count Check, Deadman Critical Check
"""
When clear the check cards filter field
Then the check cards column contains
"""
Simple Count Check, Deadman Critical Check, Veille automatique - Avertissement, Bécik
"""
Scenario: Threshold Check history - basic
When hover over the name of the check card "Simple Count Check"
# Collect some data - generate at least 1 event
When wait "10" seconds
When click open history of the check card "Simple Count Check"
When click open history confirm of the check card "Simple Count Check"
# Just check page load
# Check history will be separate test feature
Then the Check statusses page is loaded
Then there are at least "1" events in the history
Then event no "1" contains the check name "Simple Count Check"
When click the check name of event no "1"
Then the edit check overlay is loaded
Then the current edit check name is "Simple Count Check"
When dismiss edit check overlay
Then the edit check overlay is not loaded
Then the Alerting page is loaded
Then there is an alert card named "Simple Count Check"
Scenario: Deadman Check history - basic
When stop live data generator
When wait "40" seconds
When hover over the name of the check card "Deadman Critical Check"
When click open history of the check card "Deadman Critical Check"
When click open history confirm of the check card "Deadman Critical Check"
Then the Check statusses page is loaded
Then there are at least "1" events in the history
Then event no "1" contains the check name "Deadman Critical Check"
Then there is at least "1" events at level "crit"
When click the check name of event no "1"
Then the edit check overlay is loaded
Then the current edit check name is "Deadman Critical Check"
When dismiss edit check overlay
Then the edit check overlay is not loaded
Then the Alerting page is loaded
Then there is an alert card named "Deadman Critical Check"
When start live data generator
# restart live generator as above
"""
{ "pulse": 5000, "model": "count10" }
"""
# Delete Check
Scenario Template: Delete Check
When hover over the name of the check card "<NAME>"
When click delete of the check card "<NAME>"
When click delete confirm of the check card "<NAME>"
Then there is no alert card named "<NAME>"
Examples:
|NAME|
|Bécik|
|Veille automatique - Avertissement|
|Deadman Critical Check|
|Simple Count Check|
# TODO - Edit Check definition -
# Edit Check definition
# Create Endpoints {HTTP, Slack, Pager Duty}
# Add labels to Endpoints
# Filter Endpoints
# Edit Endppints
# Create Rules
# Add labels to Rules
# Filter Rules
# Edit Rules
# Delete Checks (N.B. what is affect on dependent rules?)
# Delete Endpoints (N.B. what is affect on dependent rules?)
# Delete Rules
# Tear down data generator - In After All hook - needs to be torn down after failure as well as success
# Tear down http listened - In After All hook - ditto
# NOTE - perhaps should have five features - base, checks, endpoints, rules, full monitoring (too harvest alerts
# and notifications.) - breakup planned tests above into these feature files.

View File

@ -0,0 +1,58 @@
@feature-monitoring
@monitoring-alerts
@use-live-data
Feature: Monitoring - Alerts - History
As a user I want to setup alerts
So that I can be notified of important changes in the data
Scenario: Load Initial Alerts view
Given I reset the environment
Given run setup over REST "DEFAULT"
When API sign in user "DEFAULT"
When start live data generator
# It seems 5s is the quickest we can use stably given default values in create check controls
# Tried 1s, but need to use agg function like mean so the checks do not seem to match
"""
{ "pulse": 5000, "model": "count10" }
"""
When wait "10" seconds
When create check over API from file "etc/test-data/test_threshold_check.json" for user "DEFAULT"
When create check over API from file "etc/test-data/test_deadman_crit_check.json" for user "DEFAULT"
When open the signin page
When UI sign in user "DEFAULT"
When click nav menu item "Alerting"
Then the Alerting page is loaded
# Need to create events for toggle markers
When wait "60" seconds
# Exercise controls
Scenario: Exercise Event History Controls
When hover over the name of the check card "Threshold Check from File"
When click open history of the check card "Threshold Check from File"
When click open history confirm of the check card "Threshold Check from File"
When click event history filter input
Then the event history examples dropdown is visible
When click the alert history title
Then the event history examples dropdown is not visible
When get events history graph area
When get event marker types and locations
When zoom into event markers
Then the event marker locations have changed
Then the events history graph has changed
Then the event toggle "OK" is off
Then the event toggle "CRIT" is on
When get events history graph area
When get event marker types and locations
#Unzoom
When double click history graph area
Then the event marker locations have changed
Then the events history graph has changed
Then the event toggle "OK" is off
Then the event toggle "CRIT" is on
# Toggle markers
#incl. hover bars and heads
# Filter - N.B. clear filter shows all checks

View File

@ -0,0 +1,84 @@
@feature-onboarding
@onboarding-onboarding
Feature: Onboard to Influxdbv2
Create an initial user and organization
@tested
Scenario: Onboard Basic
# Golden path check 1
Given I reset the environment
Given I open the Influx onboarding page
Then there is a Welcome message
Then there is a link to corporate
When I click on Get Started
Then the Initial Setup Page is loaded
When enter a new user name "DEFAULT"
When enter a new password "DEFAULT"
When enter confirm the new password "DEFAULT"
When enter a new organization name "DEFAULT"
When enter a new bucket name "DEFAULT"
When click next from setup page
Then verify ready page
Then the success notification says "Initial user details have been successfully set"
When click quick start button
Then the success notification contains "Metrics Dashboard has been created"
Then the success notification contains "The InfluxDB Scraper has been configured"
When close all notifications
Then the home page is loaded
@tested
Scenario: Onboard Advanced
# Golden path check 2
Given I reset the environment
Given I open the Influx onboarding page
Then there is a Welcome message
Then there is a link to corporate
When I click on Get Started
Then the Initial Setup Page is loaded
When enter a new user name "DEFAULT"
When enter a new password "DEFAULT"
When enter confirm the new password "DEFAULT"
When enter a new organization name "DEFAULT"
When enter a new bucket name "DEFAULT"
When click next from setup page
Then verify ready page
Then the success notification says "Initial user details have been successfully set"
When close all notifications
When click advanced button
Then the buckets tab is loaded
@tested
Scenario: Onboard field checks
# N.B. would expect there to be rules for min/max length or allowed/disallowed characters in user-names
# however none currently exist -- TODO add tests for such rules if they are ever implemented
# TODO - setup breadcrumb color change on password length less than 8 chars
Given I reset the environment
Given I open the Influx onboarding page
Then there is a Welcome message
Then there is a link to corporate
When I click on Get Started
Then the Initial Setup Page is loaded
Then the continue button is disabled
When enter a new user name "zaphod"
When enter a new password "chachacha"
When enter confirm the new password "achachach"
Then the form error message says "Passwords do not match"
When enter confirm the new password "chachacha"
Then the form error message is not present
When enter a new organization name "asdf"
When enter a new bucket name "asdf"
When enter a new password "asdf"
When enter confirm the new password "asdf"
Then the form error message says "Password must be at least 8 characters"
Then the continue button is disabled
When enter a new password "chachacha"
When enter confirm the new password "chachacha"
When click next from setup page
Then verify ready page
Then the success notification says "Initial user details have been successfully set"
When click quick start button
Then the success notification contains "Metrics Dashboard has been created"
Then the success notification contains "The InfluxDB Scraper has been configured"
When close all notifications
Then the home page is loaded

View File

@ -0,0 +1,152 @@
@feature-settings
@settings-labels
Feature: Settings - Labels
As a user I want to Read Create Update and Delete Labels
So that I can manage the tag items used with Influxdbv2
@tested
Scenario: Open Labels Tab
Given I reset the environment
Given run setup over REST "DEFAULT"
When open the signin page
When UI sign in user "DEFAULT"
When click nav menu item "Settings"
When click the settings tab "Labels"
Then the labels Tab is loaded
@tested
Scenario: Exercise Create Label Popup
When I click the empty state Create Label button
Then the create Label popup is loaded
When dismiss the popup
Then popup is not loaded
When I click the header Create Label button
Then the create Label popup is loaded
When cancel the create label popup
Then popup is not loaded
When I click the header Create Label button
Then the color input color matches the preview color
Then the preview label pill contains "Name this Label"
When enter the value "Etiketa" into the label popup name input
Then the preview label pill contains "Etiketa"
When clear the label popup color input
Then the form element error message is "Please enter a hexcode"
When enter the value "#" into the label popup color input
Then the form element error message is "Must be 7 characters"
When clear the label popup color input
When enter the value "#FFC0CB" into the label popup color input
Then the color input color matches the preview color
When click the color select button I'm feeling lucky
Then the value in the label popup color input is not "#FFC0CB"
Then the color input color matches the preview color
When click the label popup color swatch "Ruby"
Then the value in the label popup color input is "#BF3D5E"
Then the color input color matches the preview color
Then the label popup preview text color is "#FFFFFF"
When click the label popup color swatch "Onyx"
Then the value in the label popup color input is "#31313d"
Then the color input color matches the preview color
Then the label popup preview text color is "#FFFFFF"
When click the label popup color swatch "Laser"
Then the value in the label popup color input is "#00C9FF"
Then the color input color matches the preview color
Then the label popup preview text color is "#202028"
When dismiss the popup
Then popup is not loaded
@tested
Scenario Outline: Create Label
When I click the header Create Label button
When clear the label popup name input
When enter the value "<NAME>" into the label popup name input
When clear the label popup description input
When enter the value "<DESCRIPTION>" into the label popup description input
When set the color in the label popup to "<COLOR>"
When click the label popup Create Label button
Then there is a label card named "<NAME>" in the labels list
Then the label card "<NAME>" has a pill colored "<COLOR>"
Then the label card "<NAME>" has description "<DESCRIPTION>"
Examples:
| NAME | DESCRIPTION | COLOR |
| Buk | Fagus sylvatica | Topaz |
| Habr | Carpinus betulus | #D2691E |
| Jilm | Ulmus laevis | Thunder |
| Javor | Acer pseudoplatanus | #924544 |
| Bouleau | Betula verrucosa | #F5EAD5 |
@tested
Scenario: Edit Label
When I click the Label Card Pill "Bouleau"
Then the edit label popup is loaded
When clear the label popup name input
When enter the value "Briza" into the label popup name input
When clear the label popup description input
When enter the value "Betula pendula" into the label popup description input
When set the color in the label popup to "#ECFC31"
Then the color input color matches the preview color
When click the label popup Save Changes button
Then there is a label card named "Briza" in the labels list
Then the label card "Briza" has a pill colored "#ECFC31"
Then the label card "Briza" has description "Betula pendula"
@tested
Scenario: Sort By Name
Then the first labels are sorted as "Briza,Buk,Habr,Javor,Jilm"
When click the sort type dropdown
When click sort by item "Name Desc"
Then the first labels are sorted as "Jilm,Javor,Habr,Buk,Briza"
When click the sort type dropdown
When click sort by item "Name Asc"
Then the first labels are sorted as "Briza,Buk,Habr,Javor,Jilm"
@error-collateral
Scenario: Sort By Description
When click the sort type dropdown
When click sort by item "Properties.Description Asc"
#When click sort label by description
Then the first labels are sorted as "Javor,Briza,Habr,Buk,Jilm"
When click the sort type dropdown
When click sort by item "Properties.Description Desc"
#When click sort label by description
Then the first labels are sorted as "Jilm,Buk,Habr,Briza,Javor"
@tested
Scenario: Filter Labels
When clear the labels filter input
When enter the value "J" into the label filter
Then the first labels are sorted as "Jilm,Javor"
Then the labels "Briza,Buk,Habr" are not present
When enter the value "AV" into the label filter
Then the first labels are sorted as "Javor"
Then the labels "Briza,Buk,Habr,Jilm" are not present
When clear the labels filter input
When enter the value "betul" into the label filter
Then the first labels are sorted as "Habr,Briza"
Then the labels "Buk,Javor,Jilm" are not present
When click the sort type dropdown
When click sort by item "Name Desc"
# There is a third neutral phase to the toggle - not anymore
#When click sort label by name
Then the first labels are sorted as "Habr,Briza"
When click the sort type dropdown
When click sort by item "Name Asc"
Then the first labels are sorted as "Briza,Habr"
When clear the labels filter input
Then the first labels are sorted as "Briza,Buk,Habr,Javor,Jilm"
@tested
Scenario Outline: Delete Label
When hover over label card "<NAME>"
When click delete for the label card "<NAME>"
When click delete confirm for the label card "<NAME>"
Then the labels "<NAME>" are not present
Examples:
| NAME |
| Briza |
| Buk |
| Habr |
| Javor |
| Jilm |

View File

@ -0,0 +1,27 @@
@feature-settings
@settings-settings
Feature: Settings - Base
As a user I want to open the settings page
So that I can explore how this Influx2 installation is set up
# TODO with alerting / load data refactor now submenu is richer
Scenario: Verify Tabs
Given I reset the environment
Given run setup over REST "DEFAULT"
When open the signin page
When UI sign in user "DEFAULT"
When open page "settings" for user "DEFAULT"
When click the settings tab "Variables"
Then the variables Tab is loaded
When click the settings tab "Templates"
Then the templates Tab is loaded
When click the settings tab "Labels"
Then the labels Tab is loaded
# When click the settings tab "Tokens" # Tokens tab is no longer available
# Then the tokens Tab is loaded # Tokens tab is no longer available
# The following items are no longer present commit=bd91a81123 build_date=2020-04-07T07:57:22Z
# When click the settings tab "Profile"
# Then the org profile Tab is loaded
# When click the settings tab "Members"
# Then the members Tab is loaded

View File

@ -0,0 +1,168 @@
@feature-settings
@settings-templates
Feature: Settings - Templates
As a user I want to Read Create Update and Delete Templatess
So that I can eventually use them to create dashboards in Influxdbv2
@tested
Scenario: Open Templates Tab
Given I reset the environment
Given run setup over REST "DEFAULT"
When open the signin page
When UI sign in user "DEFAULT"
When click nav menu item "Settings"
When click the settings tab "Templates"
Then the templates Tab is loaded
Then the templates are sorted as:
"""
Apache Data,Docker,Getting Started with Flux,GitHub Data,InfluxDB 2.0 OSS Metrics,JMeter,Kubernetes,Nginx,Redis,System
"""
When API sign in user "DEFAULT"
When generate a line protocol testdata for user "DEFAULT" based on:
"""
{ "points": 120, "measurement":"level", "start": "-30d", "algo": "hydro", "prec": "sec", "name": "hydro"}
"""
When generate a line protocol testdata for user "DEFAULT" based on:
"""
{ "points": 120, "measurement":"beat", "start": "-30d", "algo": "sine", "prec": "sec", "name": "sine"}
"""
@tested
Scenario: Exercise Import Template Popup
When click user templates
When click empty state import template button
Then the import template popup is loaded
Then the Import JSON as template button is disabled
When dismiss the popup
Then popup is not loaded
When click header import template button
Then click the import template paste button
Then the Import Template file upload area is not present
Then the Import Template paste JSON text area is present
When enter into the Impprt Template paste JSON text area:
"""
{}
"""
Then the Import JSON as template button is enabled
When click the import template upload button
Then the Import JSON as template button is disabled
Then the Import Template file upload area is present
Then the Import Template paste JSON text area is not present
When dismiss the popup
Then popup is not loaded
# TODO add variables to templates
@error-collateral
Scenario Outline: Import User Template File Upload
When click user templates
When click header import template button
When upload the template file "<FILEPATH>"
When click popup submit button
Then popup is not loaded
Then the success notification contains "Successfully imported template."
When close all notifications
# sometimes page is stuck in cache
When force page refresh
When wait "10" seconds
Then a REST template document for user "DEFAULT" titled "<TITLE>" exists
# Following step is work around for issue 15514
When click user templates
Then there is a template card named "<TITLE>"
Examples:
|TITLE|FILEPATH|
|Hydro test dashboard-Template|etc/test-data/hydro-test-template.json|
|Note Dashboard-Template|etc/test-data/note-dboard-template.json|
@error-collateral
Scenario Outline: Import User Template as JSON
When click header import template button
Then click the import template paste button
When paste contents of "<FILEPATH>" to template textarea
When click popup submit button
Then popup is not loaded
Then the success notification contains "Successfully imported template."
When close all notifications
# sometimes page is stuck in cache
When force page refresh
When wait "10" seconds
Then a REST template document for user "DEFAULT" titled "<TITLE>" exists
# Following step is work around for issue 15514
When click user templates
Then there is a template card named "<TITLE>"
Examples:
|TITLE|FILEPATH|
|Sinusoid test data-Template|etc/test-data/sine-test-template.json|
|Notepad-Template|etc/test-data/notepad-test-template.json|
@error-collateral
Scenario: Import Bad Template File
When click user templates
When click header import template button
When upload the template file "etc/test-data/bad-template.json"
When click popup submit button
Then popup is not loaded
Then the error notification contains "Failed to import template: Error: Request failed with status code 400"
When close all notifications
@error-collateral
Scenario: Filter Templates
When click user templates
When enter the value "Note" into the templates filter field
Then the templates are sorted as:
"""
Note Dashboard-Template,Notepad-Template
"""
Then the template cards "Hydro test dashboard-Template,Sinusoid test data-Template" are not present
When clear the templates filter
Then the templates are sorted as:
"""
Hydro test dashboard-Template,Note Dashboard-Template,Notepad-Template,Sinusoid test data-Template
"""
@error-collateral
Scenario: Sort Templates by Name
When click the sort type dropdown
When click sort by item "Meta.Name Desc"
Then the templates are sorted as:
"""
Sinusoid test data-Template,Notepad-Template,Note Dashboard-Template,Hydro test dashboard-Template
"""
When click the sort type dropdown
When click sort by item "Meta.Name Asc"
Then the templates are sorted as:
"""
Hydro test dashboard-Template,Note Dashboard-Template,Notepad-Template,Sinusoid test data-Template
"""
#Scenario: Exercise View Template Popup
# TODO - this part of UI was being rewritten
#Scenario: Clone Template
# TODO - this part of UI was being rewritten
#Scenario: Rename Template
# TODO - this part of UI was being rewritten
# N.B. generate some labels above - to be used in this test
# Scenario: Add Labels to Template
# Issue #15547 - create new label through templates runs into this minor issue
# Scenario: Create Dashboard from Template
# Covered in Dashboard tests
@error-collateral
Scenario Outline: Delete template
When hover over template card named "<NAME>"
When click the context delete button of template "<NAME>"
When click the delete confirm button of template "<NAME>"
Then the template cards "<NAME>" are not present
Examples:
|NAME|
|Hydro test dashboard-Template|
|Note Dashboard-Template |
|Notepad-Template |
|Sinusoid test data-Template |

View File

@ -0,0 +1,349 @@
@feature-settings
@settings-variables
Feature: Settings - Variables
As a user I want to Read Create Update and Delete Variables
So that I can eventually reuse them in alerts and dashboards in Influxdbv2
Scenario: Open Variables Tab
Given I reset the environment
Given run setup over REST "DEFAULT"
When open the signin page
When clear browser storage
When UI sign in user "DEFAULT"
When click nav menu item "Settings"
When click the settings tab "Variables"
Then the variables Tab is loaded
Scenario: Exercise Import Variable Popup
When click create variable dropdown in header
When click "import" variable dropdown item
Then the import variable popup is loaded
Then dismiss the popup
Then popup is not loaded
When click create variable dropdown empty
When click "import" variable dropdown item
Then the import variable popup is loaded
When click the Import Variable popup paste JSON button
Then the Import Variable JSON textarea is visible
Then the Import JSON as variable button is enabled
When click the Import Variable popup Upload File button
Then the Import Variable JSON textarea is not visible
Then the Import JSON as variable button is not enabled
Then the Import JSON file upload area is present
Then dismiss the popup
Then popup is not loaded
@tested
Scenario: Exercise Create Variable Popup
When click create variable dropdown in header
When click "new" variable dropdown item
Then the create variable popup is loaded
Then dismiss the popup
Then popup is not loaded
When click create variable dropdown empty
When click "new" variable dropdown item
Then the create variable popup is loaded
Then the create variable popup selected type is "Query"
Then the create variable popup create button is disabled
Then the create variable popup script editor is visible
When click the create variable popup type dropdown
When click the create variable popup type dropdown item "Map"
Then the create variable popup selected type is "Map"
Then the create variable popup create button is disabled
Then the create variable popup script editor is not visible
Then the create variable popup textarea is visible
Then the create variable popup default value dropdown is visible
Then the create variable popup info line contains "0" items
When click the create variable popup type dropdown
When click the create variable popup type dropdown item "constant"
Then the create variable popup selected type is "CSV"
Then the create variable popup create button is disabled
Then the create variable popup script editor is not visible
Then the create variable popup textarea is visible
Then the create variable popup default value dropdown is visible
Then the create variable popup info line contains "0" items
When click the create variable popup type dropdown
When click the create variable popup type dropdown item "Query"
Then the create variable popup selected type is "Query"
Then the create variable popup create button is disabled
Then the create variable popup script editor is visible
Then the create variable popup textarea is not visible
Then the create variable popup default value dropdown is not visible
Then the create variable popup info line is not visible
Then click popup cancel simple button
Then popup is not loaded
Scenario Outline: Import Variable
When click create variable dropdown in header
When click "import" variable dropdown item
When upload the import variable file "<FILE>"
Then the import variable drag and drop header contains success "<FILE>"
When click the import variable import button
Then popup is not loaded
Then the success notification contains "Successfully created new variable: <NAME>"
Then close all notifications
Then there is a variable card for "<NAME>"
Examples:
|NAME|FILE|
|Bucket|etc/test-data/variable-query-bucket.json|
|Ryby|etc/test-data/variable-map-ryby.json|
|Jehlicnany|etc/test-data/variable-map-jehlicnany.json|
|Slavia|etc/test-data/variable-csv-slavia.json|
|Arsenal|etc/test-data/variable-csv-arsenal.json|
# TODO - failover on bad variable file
@tested
Scenario: Import Bad Variable
When click create variable dropdown in header
When click "import" variable dropdown item
When upload the import variable file "etc/test-data/variable-empty.json"
Then the import variable drag and drop header contains success "etc/test-data/variable-empty.json"
When click the import variable import button
Then popup is not loaded
Then the error notification contains "Failed to create variable:"
Then close all notifications
Scenario: Create Map Variable
When click create variable dropdown in header
When click "new" variable dropdown item
When click the create variable popup type dropdown
When click the create variable popup type dropdown item "map"
When clear the create variable popup name input
When enter the create variable popup name "Primaty"
When enter the create variable popup values:
"""
Human,homo sapiens
Orangutan,pongo
Chimpanzee,pan troglodytes
Gorilla,gorilla
Baboon,papio
"""
When click the create variable popup title
Then the create variable popup info line contains "5" items
Then the selected default variable dropdown item is "Human"
When click the create variable popup default dropdown
When click the create variable popup default dropdown item "Chimpanzee"
Then the selected default variable dropdown item is "Chimpanzee"
When click the create variable popup create button
Then popup is not loaded
Then the success notification contains "Successfully created new variable: Primaty"
Then close all notifications
Then there is a variable card for "Primaty"
Scenario: Create CSV Variable
When click create variable dropdown in header
When click "new" variable dropdown item
When click the create variable popup type dropdown
When click the create variable popup type dropdown item "constant"
When clear the create variable popup name input
When enter the create variable popup name "Obdobi"
When enter the create variable popup values:
"""
Antropocen,Holocen,Svrchni pleistocen,Stredni pleistocen,Spodni pleistocen
"""
When click the create variable popup title
Then the create variable popup info line contains "5" items
Then the selected default variable dropdown item is "Antropocen"
When click the create variable popup default dropdown
When click the create variable popup default csv dropdown item "Holocen"
Then the selected default variable dropdown item is "Holocen"
When click the create variable popup create button
Then popup is not loaded
Then the success notification contains "Successfully created new variable: Obdobi"
Then close all notifications
Then there is a variable card for "Obdobi"
Scenario: Create CSV Variable
When click create variable dropdown in header
When click "new" variable dropdown item
When click the create variable popup type dropdown
When click the create variable popup type dropdown item "constant"
When clear the create variable popup name input
When enter the create variable popup name "Reals"
When enter the create variable popup values:
"""
1.0,2.0,2.72,3.0,3.14
"""
When click the create variable popup title
Then the create variable popup info line contains "5" items
Then the selected default variable dropdown item is "1.0"
When click the create variable popup default dropdown
When click the create variable popup default csv dropdown item "2.0"
Then the selected default variable dropdown item is "2.0"
When click the create variable popup create button
Then popup is not loaded
Then the success notification contains "Successfully created new variable: Reals"
Then close all notifications
Then there is a variable card for "Reals"
Scenario: Create Query Variable
When click create variable dropdown in header
When click "new" variable dropdown item
When click the create variable popup type dropdown
When click the create variable popup type dropdown item "Query"
When clear the create variable popup name input
When enter the create variable popup name "Kybl"
When enter the create variable popup Monaco Editor text:
"""
buckets()
|> filter(fn: (r) => r.name !~ /^_/)
|> rename(columns: {name: '_value'})
|> keep(columns: ['_value'])
"""
When click the create variable popup create button
Then popup is not loaded
Then the success notification contains "Successfully created new variable: Kybl"
Then close all notifications
Then there is a variable card for "Kybl"
Scenario: Filter Variables By Name
When enter the value "yb" into the variables filter
Then the variable cards "Kybl,Ryby" are visible
Then the variable cards "Arsenal,Bucket,Jehlicnany,Obdobi,Primaty,Reals,Slavia" are not present
Then the variable cards are sorted as "Kybl,Ryby"
When click the sort type dropdown
When click sort by item "Name Desc"
Then the variable cards are sorted as "Ryby,Kybl"
When click the sort type dropdown
When click sort by item "Name Asc"
#When click the variable sort by name button
When clear the variables filter
Then the variable cards "Arsenal,Bucket,Jehlicnany,Kybl,Obdobi,Primaty,Reals,Ryby,Slavia" are visible
# Sort by type not working - TODO fix after Issue 15379 fixed
Scenario: Sort Variables by name
Then the variable cards are sorted as "Arsenal,Bucket,Jehlicnany,Kybl,Obdobi,Primaty,Reals,Ryby,Slavia"
When click the sort type dropdown
When click sort by item "Name Desc"
#When click the variable sort by name button
# has third neutral state -- not any more
#When click the variable sort by name button
Then the variable cards are sorted as "Slavia,Ryby,Reals,Primaty,Obdobi,Kybl,Jehlicnany,Bucket,Arsenal"
When click the sort type dropdown
When click sort by item "Name Asc"
#When click the variable sort by name button
Then the variable cards are sorted as "Arsenal,Bucket,Jehlicnany,Kybl,Obdobi,Primaty,Reals,Ryby,Slavia"
Scenario: Rename Variable
When hover over variable card named "Ryby"
When click the context menu of the variable "Ryby"
When click the context menu item "Rename" of the variable "Ryby"
Then the variable name warning popup is visible
Then dismiss the popup
Then popup is not loaded
When hover over variable card named "Ryby"
When click the context menu of the variable "Ryby"
When click the context menu item "Rename" of the variable "Ryby"
Then the variable name warning popup is visible
When click the rename variable warning popup understand button
When click popup cancel simple button
Then popup is not loaded
When hover over variable card named "Ryby"
When click the context menu of the variable "Ryby"
When click the context menu item "Rename" of the variable "Ryby"
When click the rename variable warning popup understand button
When clear the rename variable popup name input
Then the rename variable form warning states "Variable name cannot be empty"
Then the rename variable from warning icon is visible
Then the rename variable submit button is disabled
When enter the new variable name "Kocky"
When click rename variable popup submit button
Then the success notification contains "Successfully updated variable: Kocky."
Then close all notifications
Then there is a variable card for "Kocky"
Scenario: Edit Map Variable to CSV
When click the variable card name "Kocky"
Then the edit variable popup is loaded
When click the edit variable popup type dropdown
When click the edit variable popup type dropdown item "constant"
Then the edit variable name input is disabled
When enter the edit variable popup values:
"""
Angora,Siamska,Barmska,Kartuzska,Ruska Modra
"""
When click the edit variable popup title
Then the create variable popup info line contains "5" items
Then the selected default variable dropdown item is "Angora"
When click the edit variable popup default dropdown
When click the edit variable popup default csv dropdown item "Kartuzska"
Then the selected default variable dropdown item is "Kartuzska"
When click the edit variable popup submit button
Then popup is not loaded
Then the success notification contains "Successfully updated variable: Kocky."
Then close all notifications
Then there is a variable card for "Kocky"
Scenario: Edit CSV Variable to Query
When click the variable card name "Slavia"
Then the edit variable popup is loaded
When click the edit variable popup type dropdown
When click the edit variable popup type dropdown item "query"
Then the edit variable name input is disabled
When enter the edit variable popup Query text:
"""
buckets()
|> filter(fn: (r) => r.name !~ /^_/)
|> rename(columns: {name: '_value'})
|> keep(columns: ['_value'])
"""
When click the edit variable popup title
When click the edit variable popup submit button
Then popup is not loaded
Then the success notification contains "Successfully updated variable: Slavia."
Then close all notifications
Then there is a variable card for "Slavia"
Scenario: Edit Query Variable to Map
When click the variable card name "Kybl"
Then the edit variable popup is loaded
When click the edit variable popup type dropdown
When click the edit variable popup type dropdown item "map"
Then the edit variable name input is disabled
Then the edit variable popup textarea is cleared
When enter the edit variable popup values:
"""
kolac,tvarohovy
kobliha,jahodova
buchta,svestkova
babovka,vanilkova
loupak,cokoladovy
pernik,domaci
"""
When click the edit variable popup title
Then the create variable popup info line contains "6" items
Then the selected default variable dropdown item is "kolac"
When click the edit variable popup default dropdown
When click the edit variable popup default csv dropdown item "babovka"
Then the selected default variable dropdown item is "babovka"
When click the edit variable popup submit button
Then popup is not loaded
Then the success notification contains "Successfully updated variable: Kybl."
Then close all notifications
Then there is a variable card for "Kybl"
@tested
Scenario Outline: Delete Variable
When hover over variable card named "<NAME>"
When click delete menu of variable card named "<NAME>"
When click delete confirm of variable card named "<NAME>"
Then the success notification contains "Successfully deleted the variable"
Then the variable card "<NAME>" is not present
Examples:
|NAME|
|Kybl|
|Kocky |
|Jehlicnany|
|Slavia |
|Arsenal |
|Bucket |
|Obdobi |
|Primaty |
|Reals |

View File

@ -0,0 +1,36 @@
@feature-signin
@signin-signin
Feature: Signin
Use and abuse the signin page
Scenario: Basic Signin
Given I reset the environment
Given run setup over REST "DEFAULT"
When open the signin page
When clear browser storage
#Then the heading contains "InfluxData"
Then the InfluxData heading is visible
Then the version shown contains "DEFAULT"
#Then the credits are valid
When enter the username "DEFAULT"
When enter the password "DEFAULT"
When click the signin button
Then the home page is loaded
@tested
Scenario Outline: Signin Bad Credentials
Given I reset the environment
Given run setup over REST "DEFAULT"
When open the signin page
When enter the username "<USERNAME>"
When enter the password "<PASSWORD>"
When click the signin button
Then the error notification contains "<MESSAGE>"
Examples:
| USERNAME | PASSWORD | MESSAGE |
| wumpus | DEFAULT | Could not sign in |
| DEFAULT | wuumpuus | Could not sign in |
# N.B. TODO - consider Security scenarios - Brute force password, injection attack

144
e2e/hooks.js Normal file
View File

@ -0,0 +1,144 @@
const fs = require('fs')
var {Before, BeforeAll, After, AfterAll, Status} = require('cucumber')
var {logging} = require('selenium-webdriver');
/*
Before(function (scenario, callback) {
callback();
});
BeforeAll(async function (scenario, callback) {
// await __wdriver.get('chrome://settings/clearBrowserData').then(async () => {
// console.log("DEBUG clearing browser cache");
// await __wdriver.sleep(3000);
// await __wdriver.switchTo().activeElement();
//// await __wdriver.findElement(By.css("* /deep/ #clearBrowsingDataConfirm")).click();
// await __wdriver.findElement(By.css("clearBrowsingDataConfirm")).click();
// await __wdriver.sleep(3000);
// }).catch(async err => {
// console.log("DEBUG caught err " + err);
// throw err;
// });
callback();
})*/
async function writeScreenShot(filename) {
filename = filename.replace(/\s+/g, '_');
return await __wdriver.takeScreenshot().then(async (image, err) => {
await fs.writeFile(filename, image, 'base64', (err) => {
if (err) {
console.log(err)
}
})
return image
})
}
async function writeConsoleLog(filename){
filename = filename.replace(/\s+/g, '_');
await __wdriver.manage().logs().get(logging.Type.BROWSER).then(async logs => {
for(let log in logs){
fs.appendFileSync(filename, `[${(new Date(parseInt(logs[log].timestamp))).toISOString()}]${logs[log].level}:${logs[log].message}\n`);
}
})
}
async function writeDriverLog(filename){
filename = filename.replace(/\s+/g, '_');
await __wdriver.manage().logs().get(logging.Type.DRIVER).then(async logs => {
for(let log in logs){
fs.appendFileSync(filename, `[${(new Date(parseInt(logs[log].timestamp))).toISOString()}]:${logs[log].message}\n`);
}
})
}
let scenarioCt = 0;
let currentFeature = '';
Before(async function (scenario){
//safety kill any live data generator
if(!await scenarioContainsTag(scenario, '@use-live-data') && __liveDataGenRunning){
console.log("killing live generator");
__killLiveDataGen = true;
}
});
async function scenarioContainsTag(scenario, tag){
let match = scenario.pickle.tags.find( elem => elem.name === tag)
//console.log("DEBUG match " + match);
return match !== undefined;
}
After(async function (scenario /*, callback */) {
//__wdriver.sleep(1500) //DEBUG - getting shots out of order
// Handled in cucumber.js
// if(!fs.existsSync(`./${__config.screenshot_dir}`)){
// fs.mkdir(`./${__config.screenshot_dir}`, () => {})
// }
let uri = scenario.sourceLocation.uri
let feature = uri.substring(uri.lastIndexOf("/") + 1).replace('.','-')
let name = scenario.pickle.name.trim().replace(' ', '_');
name = name.replace('/', '-');
name = name.replace('\\', '-');
if(feature !== currentFeature){
scenarioCt = 0;
}
if(scenarioCt === 0){
currentFeature = feature;
}
// let now = new Date()
// let nowStr = now.getFullYear().toString() +
// (now.getMonth() + 1).toString().padStart(2, '0') +
// now.getDate().toString().padStart(2, '0') + "-" +
// now.getHours().toString().padStart(2, '0') +
// now.getMinutes().toString().padStart(2, '0') +
// now.getSeconds().toString().padStart(2, '0')
//let filebase = __config.screenshot_dir + '/' + feature + "-" + nowStr + "-" + name
let filebase = __screenShotDir + "/" + feature + "-" + (scenarioCt++).toString().padStart(2, '0') + "-" + name;
let world = this;
if(scenario.result.status === Status.FAILED){
await writeScreenShot(filebase + "-ERR" + ".png").then(async img => {
await world.attach(img, 'image/png')
});
await writeConsoleLog(filebase + '-ERR-console.log').catch(async e => {
throw('failed to write ' + filebase + '-ERR-console.log\n' + e);
});
await writeDriverLog(filebase + '-ERR-driver.log').catch(async e => {
throw('failed to write ' + filebase + '-ERR-console.log\n' + e);
})
}else {
await writeScreenShot(filebase + "--OK" + ".png").then(async img => {
await world.attach(img, 'image/png')
})
}
//callback()
});
AfterAll(async function ( ) {
if(__liveDataGenRunning) {
console.log("killing live generator");
__killLiveDataGen = true;
}
});

37
e2e/package.json Normal file
View File

@ -0,0 +1,37 @@
{
"name": "selenium-accept",
"version": "0.0.1",
"description": "selenium acceptance tests for influxdbv2 ui",
"main": "index.js",
"scripts": {
"clean": "rm -rdf screenshots/*; rm report/*",
"influx:setup": "cd scripts && ./influxdb2_community_test_env.sh setup-qa && cd -",
"influx:stop": "./scripts/influxdb2_community_test_env.sh stop",
"influx:clean": "./scripts/influxdb2_community_test_env.sh clean",
"test": "mkdirp report && cucumber-js",
"report:html": "chmod 775 src/utils/htmlReport.js; src/utils/htmlReport.js",
"report:junit": "chmod 775 src/utils/junitReport.js; src/utils/junitReport.js",
"kill:chrome": "killall chrome || true"
},
"author": "",
"license": "ISC",
"devDependencies": {
"axios": "^0.19.0",
"babel-cli": "^6.26.0",
"babel-core": "^6.26.3",
"babel-preset-env": "^1.7.0",
"chai": "^4.2.0",
"chai-match": "^1.1.1",
"chromedriver": "^80.0.0",
"copy-paste": "^1.3.0",
"csv": "^5.3.1",
"cucumber": "^5.1.0",
"cucumber-html-reporter": "^5.0.0",
"cucumber-junit-convert": "^1.0.2",
"cucumber-pretty": "^1.5.2",
"eslint": "^6.1.0",
"geckodriver": "^1.16.2",
"mkdirp": "^0.5.1",
"selenium-webdriver": "^4.0.0-alpha.7"
}
}

3
e2e/scripts/Dockerfile Normal file
View File

@ -0,0 +1,3 @@
FROM quay.io/influxdb/influx:nightly
ENTRYPOINT ["/entrypoint.sh", "--e2e-testing=true"]

View File

@ -0,0 +1,18 @@
FROM circleci/node:lts-stretch-browsers
USER root
COPY package.json /selenium-accept-infl2/package.json
WORKDIR /selenium-accept-infl2
RUN npm install
COPY . /selenium-accept-infl2
RUN \
mkdir report
ENV PATH $PATH:./node_modules/.bin
CMD cucumber-js

33
e2e/scripts/circleci-run.sh Executable file
View File

@ -0,0 +1,33 @@
!#/usr/bin/env bash
sleep 30
sudo netstat -tlnp
curl -v --connect-timeout 60 --max-time 60 http://localhost:9999/debug/flush
git clone https://github.com/influxdata/influxdb.git
cd ~/project/influxdb/e2e/
npm install
npm test -- features/loadData/telegrafs.feature
TEST_RESULT=$?
echo TEST_RESULT = $TEST_RESULT
npm run report:junit
npm run report:html
echo "Saving Test Results"
mkdir -p ~/test-results/cucumber
mkdir -p ~/test-results/html
pwd
cp ~/project/influxdb/e2e/report/cucumber_junit.xml ~/test-results/cucumber/junit.xml
cp ~/project/influxdb/e2e/report/cucumber_report.html ~/test-results/html/cucumber_report.html
cp ~/project/influxdb/e2e/report/cucumber_report.json ~/test-results/cucumber/report.cucumber
cp -r ~/project/influxdb/e2e/screenshots ~/test-results
exit $TEST_RESULT

View File

@ -0,0 +1,200 @@
#!/bin/sh
USE_DOCKER=1 # Docker or tarball
USE_ALPHA=0 # alpha or nightly
DEBUG=0
ACTION="setup"
TAR_DOWNLOAD="https://dl.influxdata.com/influxdb/releases/influxdb_2.0.0-alpha.1_linux_amd64.tar.gz"
DOCKER_IMAGE="quay.io/influxdb/influx:nightly"
INSTANCE_NAME="influx2_solo"
INFLUX2_HOME="${HOME}/.influxdbv2"
LOG_DIR=${PWD}/log
LOG_FILE=${LOG_DIR}/docker.log
TELEGRAF_DOWNLOAD="https://dl.influxdata.com/telegraf/releases/telegraf_1.8.2-1_amd64.deb"
download_telegraf(){
echo "["$(date +"%d.%m.%Y %T")"] downloading telegraf.deb - if changed"
#curl -s -o telegraf.deb -z telegraf.deb https://dl.influxdata.com/telegraf/releases/telegraf_1.7.2-1_amd64.deb
curl -s -o telegraf.deb -z telegraf.deb https://dl.influxdata.com/telegraf/releases/telegraf_1.8.2-1_amd64.deb
}
install_telegraf(){
echo "["$(date +"%d.%m.%Y %T")"] installing telegraf.deb - if changed"
sudo dpkg -i telegraf.deb
echo "["$(date +"%d.%m.%Y %T")"] installed telegraf.deb - if changed"
}
stop_telegraf(){
echo "["$(date +"%d.%m.%Y %T")"] stoping telegraf.service - if changed"
sudo systemctl stop telegraf.service
echo "["$(date +"%d.%m.%Y %T")"] stoped telegraf.service - if changed"
}
pull_docker(){
echo "["$(date +"%d.%m.%Y %T")"] pulling ${DOCKER_IMAGE}"
docker pull ${DOCKER_IMAGE}
}
run_docker_influx(){
mkdir -p ${LOG_DIR}
echo "["$(date +"%d.%m.%Y %T")"] starting docker instance ${INSTANCE_NAME}"
sudo docker run --name ${INSTANCE_NAME} --publish 9999:9999 ${DOCKER_IMAGE} > ${LOG_FILE} 2>&1 &
echo "["$(date +"%d.%m.%Y %T")"] started instance $INSTANCE_NAME listening at port 9999."
echo "logfile at $LOG_FILE"
sleep 3
echo "\n$(tail -n32 $LOG_FILE)\n"
}
run_docker_influx_test_env(){
mkdir -p ${LOG_DIR}
sudo docker stop $INSTANCE_NAME
sudo docker rm $INSTANCE_NAME
sudo docker pull quay.io/influxdb/influx:nightly
sudo docker build -t influxdb_test_image .
sudo docker run --name $INSTANCE_NAME --publish 9999:9999 influxdb_test_image > ${LOG_FILE} 2>&1 &
}
stop_docker_influx(){
if [ "$(sudo docker ps -q -f name=$INSTANCE_NAME)" ] ; then
echo "["$(date +"%d.%m.%Y %T")"] stopping docker instance ${INSTANCE_NAME}"
sudo docker stop ${INSTANCE_NAME}
echo "["$(date +"%d.%m.%Y %T")"] stopped $INSTANCE_NAME"
fi
}
remove_docker_influx(){
if [ "$(sudo docker ps -a -q -f name=$INSTANCE_NAME)" ] ; then
echo "["$(date +"%d.%m.%Y %T")"] removing docker instance ${INSTANCE_NAME}"
sudo docker rm ${INSTANCE_NAME}
echo "["$(date +"%d.%m.%Y %T")"] removed $INSTANCE_NAME"
fi
}
clean_influx_home(){
echo "["$(date +"%d.%m.%Y %T")"] cleaning ${INFLUX2_HOME}"
sudo rm -rf ${INFLUX2_HOME}
echo "["$(date +"%d.%m.%Y %T")"] cleaned ${INFLUX2_HOME}"
}
usage(){
echo "usage $0"
echo " -a|--alpha use alpha release - otherwise nightly build is used"
echo " -t|--tarball use tarball build - otherwise docker is used"
echo " -n|--name set name of docker container - default $INSTANCE_NAME"
echo " setup|start start the service daemon - default action"
echo " shutdown|stop shutdown the service daemon"
echo " clean clean(remove) local directory ${INFLUX_HOME}"
echo " -h|--help print this message"
}
# Will need to get the telegraf config on first creating org
# then restart telegraf... Will be part of test cases
while [ "$1" != "" ]; do
case $1 in
-a | --alpha ) USE_ALPHA=1
DOCKER_IMAGE="quay.io/influxdb/influxdb:2.0.0-alpha"
;;
-t | --tarball ) USE_DOCKER=0
;;
-n | --name ) shift
INSTANCE_NAME=$1
;;
setup | start ) ACTION="setup"
;;
setup-qa | start-qa ) ACTION="setup-qa"
;;
stop | shutdown ) ACTION="stop"
;;
clean ) ACTION="clean"
;;
-h | --help ) usage
exit
;;
-d | --debug ) DEBUG=1
;;
* ) usage
exit 1
esac
shift
done
if [ $DEBUG -gt 0 ] ; then
echo "USE_DOCKER $USE_DOCKER"
echo "USE_ALPHA $USE_ALPHA"
echo "ACTION $ACTION"
echo "TAR_DOWNLOAD $TAR_DOWNLOAD"
echo "DOCKER_IMAGE $DOCKER_IMAGE"
echo "INSTANCE_NAME $INSTANCE_NAME"
echo "INFLUX2_HOME $INFLUX2_HOME"
echo "LOG_FILE $LOG_FILE"
echo "TELEGRAF_DOWNLOAD $TELEGRAF_DOWNLOAD"
fi
if [ $USE_ALPHA -ne 0 ] ; then
echo "USING ALPHA"
fi
if [ $USE_ALPHA -eq 0 ] && [ $USE_DOCKER -eq 0 ] ; then
echo "Nightly builds with tar balls not supported at this time"
exit 1
fi
if [ $USE_DOCKER -eq 0 ] ; then
echo "tarball install - not yet supported"
exit
fi
#if [ $USE_ALPHA -eq 0 ] && [ $USE_DOCKER -gt 0 ] ; then
case $ACTION in
setup ) stop_docker_influx
remove_docker_influx
download_telegraf
pull_docker
run_docker_influx
;;
setup-qa) run_docker_influx_test_env
;;
stop ) stop_docker_influx
;;
clean ) clean_influx_home
;;
* ) echo "Unhandled ACTION $ACTION"
exit 1
esac
#if [ "$ACTION" = "setup" ] ; then
# stop_docker_influx
# remove_docker_influx
# download_telegraf
# install_telegraf
# stop_telegraf
# pull_docker_nightly
# run_docker_influx
#elif [ "$ACTION" = "stop" ] ; then # stop docker nightly
# stop_docker_influx
# else
# echo "Unhandled ACTION $ACTION"
# exit 1
#fi
#fi
#if [ -z $1 ] ; then # check if first cmdline param is not set
# echo "["$(date +"%d.%m.%Y %T")"] USAGE: $0 [setup|start|stop]"
#elif [ "$1" = "setup" ] || [ "$1" = "start" ] ; then
# stop_docker_influx
# remove_docker_influx
# download_telegraf
## install_telegraf
## stop_telegraf
# pull_docker_nightly
# run_docker_influx
#elif [ "$1" = "stop" ] ; then
# stop_docker_influx
#fi

394
e2e/src/pages/basePage.js Normal file
View File

@ -0,0 +1,394 @@
const { By, Condition, until, StaleElementReferenceError} = require('selenium-webdriver');
const notificationSuccessMsg = '[data-testid=notification-success] .notification--message';
const notificationErrorMsg = '[data-testid=notification-error] .notification--message';
const notificationPrimaryMsg = '[data-testid=notification-primary] .notification--message';
const notificationCloseButton = '[data-testid^=notification-] button';
const popupOverlayContainer = '[data-testid=overlay--container]';
const popupFormElementError = '[data-testid=form--element-error]';
const formInputError = '[data-testid=input-error]';
const popupOverlay = '[data-testid=overlay]';
const popupDismiss = '[data-testid=overlay--header] button[class*=dismiss]';
const popupCancel = '[data-testid=overlay--container] button[data-testid=button--cancel]';
const popupCancelSimple = '[data-testid=overlay--container] button[title=Cancel]';
const popupWizardContinue = '[data-testid=overlay--body] [data-testid=next]';
const popupSave = '[data-testid=overlay--container] button[data-testid=button--save] ';
const popupSaveSimple = '[data-testid=overlay--footer] button[title=\'Save\']';
const popupCreate = '[data-testid=overlay--container] button[title=Create]';
const popupSubmit = '[data-testid=button][type=submit]';
const popupCopyToClipboard = '[data-testid=button-copy][title=\'Copy to Clipboard\']';
const popupWizardBack = '[data-testid=overlay--body] [data-testid=back]';
const popupWizardTitle = '[data-testid=overlay--body] .wizard-step--title';
const popupWizardSubTitle = '[data-testid=overlay--body] .wizard-step--sub-title';
const popupWizardDocsLink = '[data-testid=overlay--body] [data-testid=docs-link]';
const popupWizardStepStateText = 'p.line-protocol--status:nth-of-type(1)';
const popupTitle = '[data-testid=overlay--header] .cf-overlay--title';
const codeMirror = 'div.CodeMirror';
const monacoEditor = '.monaco-editor';
const popupAlert = '[data-testid=alert]';
const popupFileUpload = 'input[type=file]';
const popupFileUploadHeader = '.drag-and-drop--header';
const pageTitle = '[data-testid=page-title] ';
const popupBody = '[data-testid=overlay--body]';
const popupGithubLink = '//a[contains(text(), \'GitHub Repository\')]';
const popoverDialog = '[data-testid=popover--dialog]';
//generic controls
const sortTypeButton = '[data-testid=resource-sorter--button]';
const sortTypeListItem = '[data-testid=\'resource-sorter--%ITEM%\']';
const dropdownContents = '[data-testid=dropdown-menu--contents]';
const dropdownItemByText = '//*[@data-testid=\'dropdown-item\'][./*[text()=\'%TEXT%\']]';
//common controls
const labelPopover = '[data-testid=\'inline-labels--popover--dialog\']';
const labelListItem = '[data-testid=\'label-list--item %ITEM%\']';
const labelPopoverFilterField = '[data-testid=\'inline-labels--popover-field\']';
const labelPopoverCreateNewButton = '[data-testid=\'inline-labels--create-new\']';
class basePage{
constructor(driver){
this.driver = driver;
}
delay(timeout){
return new Promise((resolve) => {
setTimeout(resolve, timeout);
});
}
async waitUntilElementCss(selector){
await this.driver.wait(until.elementLocated(By.css(selector)));
}
async waitUntilElementVisibleCss(selector){
await this.driver.wait(until.elementIsVisible(this.driver.findElement(By.css(selector))));
}
// selectors should be array of {type, selector}
async isLoaded(selectors, url = undefined){
if(url){
await this.driver.wait(until.urlContains(url), 5000);
}
//selectors.forEach(async (selector) => { //N.B. for each seems to be swallowing thrown errors
for(let i = 0; i < selectors.length; i++) {
switch (selectors[i].type) {
case 'css':
await this.driver.wait(until.elementLocated(By.css(selectors[i].selector)), 5000);
break;
case 'xpath':
await this.driver.wait(until.elementLocated(By.xpath(selectors[i].selector)), 5000);
break;
default:
throw `Unkown selector type ${JSON.stringify(selectors[i])}`;
}
// TODO - implement other selector types
}
//});
}
async getPopupOverlay(){
return await this.driver.findElement(By.css(popupOverlay));
}
static getPopupOverlaySelector(){
return { type: 'css', selector: popupOverlay};
}
async getNoficicationSuccessMsgs(){
await this.waitUntilElementVisibleCss(notificationSuccessMsg);
return await this.driver.findElements(By.css(notificationSuccessMsg));
}
async getNotificationErrorMsgs(){
await this.waitUntilElementVisibleCss(notificationErrorMsg);
return await this.driver.findElements(By.css(notificationErrorMsg));
}
async getNotificationPrimaryMsgs(){
await this.waitUntilElementVisibleCss(notificationPrimaryMsg);
return await this.driver.findElements(By.css(notificationPrimaryMsg));
}
async getNotificationCloseButtons(){
return await this.driver.findElements(By.css(notificationCloseButton));
}
// selector shold be of {type, selector}
// helper to avoid stale element exceptions etc.
async smartGetElement(selector, timeout = this.driver.manage().getTimeouts().implicit) {
let resultElem; // check staleness with resultElem.enabled() or similar
for (let i = 0; i < 3; i++) {
try {
switch (selector.type) {
case 'css':
await this.driver.wait(until.elementLocated(By.css(selector.selector)), timeout);
resultElem = await this.driver.findElement(By.css(selector.selector)).catch(async err => {
console.log('DEBUG CAUGHT ERROR ' + JSON.stringify(err));
console.log('AT ' + selector.selector);
throw err;
});
break;
case 'xpath':
await this.driver.wait(until.elementLocated(By.xpath(selector.selector)), timeout);
resultElem = await this.driver.findElement(By.xpath(selector.selector)).catch(async err => {
console.log('DEBUG CAUGHT ERROR ' + JSON.stringify(err));
console.log('AT ' + selector.selector);
throw err;
});
break;
default:
throw `Unkown selector type ${JSON.stringify(selector)}`;
}
await resultElem.isEnabled();
} catch (e) {
if (e instanceof StaleElementReferenceError && i !== 2) {
console.log('DEBUG caught ' + e);
//continue - try to get elem again
} else {
throw e;
}
}
}
return resultElem;
}
// selector should be of {type, selector}
async getUntilElementNotPresent(selector){
return new Condition('for no element to be located ' + selector, async () => {
switch(selector.type) {
case 'css':
return await this.driver.findElements(By.css(selector.selector)).then(function (elements) {
return elements.length === 0;
});
case 'xpath:':
return await this.driver.findElements(selector).then(function (elements) {
return elements.length === 0;
});
default:
throw `Unkown selector type ${JSON.stringify(selector)}`;
}
});
}
async getPopupOverlayContainer(){
return await this.driver.findElement(By.css(popupOverlayContainer));
}
static getPopupOverlayContainerSelector(){
return { type: 'css', selector: popupOverlayContainer};
}
async getPopupFormElementError(){
return await this.driver.findElement(By.css(popupFormElementError));
}
static getPopupFormElementErrorSelector(){
return { type: 'css', selector: popupFormElementError};
}
async getFormInputErrors(){
return await this.driver.findElements(By.css(formInputError));
}
static getFormInputErrorSelector(){
return { type: 'css', selector: formInputError};
}
async getPopupDismiss(){
return await this.driver.findElement(By.css(popupDismiss));
}
async getPopupCancel(){
return await this.driver.findElement(By.css(popupCancel));
}
async getPopupCancelSimple(){
return await this.driver.findElement(By.css(popupCancelSimple));
}
async getPopupSave(){
return await this.driver.findElement(By.css(popupSave));
}
async getPopupSaveSimple(){
return await this.driver.findElement(By.css(popupSaveSimple));
}
async getPopupSubmit(){
return await this.driver.findElement(By.css(popupSubmit));
}
async getPopupWizardContinue(){
return await this.driver.findElement(By.css(popupWizardContinue));
}
static getPopupWizardContinueSelector(){
return {type: 'css', selector: popupWizardContinue};
}
async getPopupWizardBack(){
return await this.driver.findElement(By.css(popupWizardBack));
}
static getPopupWizardBackSelector(){
return {type: 'css', selector: popupWizardBack};
}
async getPopupWizardTitle(){
return await this.driver.findElement(By.css(popupWizardTitle));
}
static getPopupWizardTitleSelector(){
return {type: 'css', selector: popupWizardTitle};
}
async getPopupWizardSubTitle(){
return await this.driver.findElement(By.css(popupWizardSubTitle));
}
static getPopupWizardSubTitleSelector(){
return {type: 'css', selector: popupWizardSubTitle};
}
async getPopupWizardDocsLink(){
return await this.driver.findElement(By.css(popupWizardDocsLink));
}
async getPopupWizardStepStateText(){
return await this.driver.findElement(By.css(popupWizardStepStateText));
}
async getPopupTitle(){
return await this.driver.findElement(By.css(popupTitle));
}
async getCodeMirror(){
return await this.driver.findElement(By.css(codeMirror));
}
async getMonacoEditor(){
return await this.driver.findElement(By.css(monacoEditor));
}
async getPopupAlert(){
return await this.driver.findElement(By.css(popupAlert));
}
async getPopupCopyToClipboard(){
return await this.driver.findElement(By.css(popupCopyToClipboard));
}
async getPopupCreate(){
return await this.driver.findElement(By.css(popupCreate));
}
async getPopupFormElementMessage(){
return await this.driver.findElement(By.css(popupFormElementError));
}
async getPopupFileUpload(){
return await this.driver.findElement(By.css(popupFileUpload));
}
async getPopupFileUploadSelector(){
return {type: 'css', selector: popupFileUpload};
}
async getPopupFileUploadHeader(){
return await this.driver.findElement(By.css(popupFileUploadHeader));
}
async getPageTitle(){
return await this.driver.findElement(By.css(pageTitle));
}
async getPopupBody(){
return await this.driver.findElement(By.css(popupBody));
}
static getPopupBodySelector(){
return { type: 'css', selector: popupBody };
}
async getPopupGithubLink(){
return await this.driver.findElement(By.xpath(popupGithubLink));
}
async getSortTypeButton(){
return await this.driver.findElement(By.css(sortTypeButton));
}
static getSortTypeButtonSelector(){
return { type: 'css', selector: sortTypeButton }
}
async getSortTypeListItem(item, normalize = true){
if(normalize) {
return await this.driver.findElement(By.css(sortTypeListItem.replace('%ITEM%', item.toLowerCase()
.replace(" ", "-"))));
}else{
return await this.driver.findElement(By.css(sortTypeListItem.replace('%ITEM%', item)));
}
}
async getDropdownContents(){
return await this.driver.findElement(By.css(dropdownContents));
}
static getDropdownContentsSelector(){
return { type: 'css', selector: dropdownContents };
}
async getDropdownItemByText(text){
return await this.driver.findElement(By.xpath(dropdownItemByText.replace('%TEXT%', text.trim())));
}
async getPopoverDialog(){
return await this.driver.findElement(By.css(popoverDialog));
}
static getpopoverDialogSelector(){
return { type: 'css', selector: popoverDialog };
}
async getLabelPopover(){
return await this.driver.findElement(By.css(labelPopover));
}
static getLabelPopoverSelector(){
return { type: 'css', selector: labelPopover };
}
async getLabelListItem(item){
return await this.driver.findElement(By.css(labelListItem.replace('%ITEM%', item)));
}
static getLabelListItemSelector(item){
return { type: 'css', selector: labelListItem.replace('%ITEM%', item) };
}
async getLabelPopoverFilterField(){
return await this.driver.findElement(By.css(labelPopoverFilterField));
}
async getLabelPopoverCreateNewButton(){
return await this.driver.findElement(By.css(labelPopoverCreateNewButton));
}
static getLabelPopoverCreateNewButtonSelector(){
return { type: 'css', selector: labelPopoverCreateNewButton };
}
}
module.exports = basePage;

View File

@ -0,0 +1,51 @@
const basePage = require(__srcdir + '/pages/basePage.js');
const { By } = require('selenium-webdriver');
const formOverlay = '[data-testid=overlay--children]';
const formOverlayHeader = '[data-testid=overlay--header]';
const formOverlayDismiss = '[data-testid=overlay--header] button.cf-overlay--dismiss';
const inputOrgName = '[placeholder*=organization]';
const inputBucketName = '[placeholder*=bucket]';
const buttonCancel = 'button[title=Cancel]';
const buttonCreate = 'button[title=Create]';
const urlCtx = 'orgs/new';
class createOrgPage extends basePage {
constructor(driver){
super(driver);
}
async isLoaded(){
await super.isLoaded([{type: 'css', selector: formOverlay},
{type: 'css', selector: formOverlayHeader},
{type: 'css', selector: formOverlayDismiss},
{type: 'css', selector: inputOrgName},
{type: 'css', selector: inputBucketName},
{type: 'css', selector: buttonCancel},
{type: 'css', selector: buttonCreate},
], urlCtx);
}
async getInputOrgName(){
return await this.driver.findElement(By.css(inputOrgName));
}
async getInputBucketName(){
return await this.driver.findElement(By.css(inputBucketName));
}
async getbuttonCancel(){
return await this.driver.findElement(By.css(buttonCancel));
}
async getbuttonCreate(){
return await this.driver.findElement(By.css(buttonCreate));
}
}
module.exports = createOrgPage;

View File

@ -0,0 +1,518 @@
const influxPage = require(__srcdir + '/pages/influxPage.js');
const { By } = require('selenium-webdriver');
const timeMachineOverlay = '[data-testid=overlay]';
//const cellTitle = '[data-testid=overlay] [data-testid=page-header--left] [data-testid=page-title]';
const cellTitle = '[data-testid=overlay] [data-testid=page-header] [data-testid=page-title]';
const cellNameInput = '[data-testid=overlay] [data-testid=page-header] [data-testid=input-field]';
//const viewTypeDropdown = '[data-testid=overlay] [data-testid=page-header--right] [data-testid=\'view-type--dropdown\']';
const viewTypeDropdown = '[data-testid=overlay] [data-testid=page-control-bar--left] [data-testid=\'view-type--dropdown\']';
const viewTypeListContents = '[data-testid=\'view-type--dropdown\'] [data-testid=dropdown-menu--contents]';
const viewTypeItem = '[data-testid=\'view-type--%ITEM%\']';
//const customizeButton = '[data-testid=overlay] [data-testid=page-header--right] [data-testid=\'cog-cell--button\']';
const customizeButton = '[data-testid=overlay] [data-testid=page-control-bar--left] [data-testid=\'cog-cell--button\']';
//const editCancel = '[data-testid=overlay] [data-testid=page-header--right] [data-testid=\'cancel-cell-edit--button\']';
const editCancel = '[data-testid=overlay] [data-testid=page-control-bar--right] [data-testid=\'cancel-cell-edit--button\']';
//const saveCell = '[data-testid=overlay] [data-testid=page-header--right] [data-testid=\'save-cell--button\']';
const saveCell = '[data-testid=overlay] [data-testid=page-control-bar--right] [data-testid=\'save-cell--button\']';
const TMTop = '.time-machine--top';
const TMBottom = '[data-testid=\'time-machine--bottom\']';
const TMViewEmptyGraphQueries = '[data-testid=overlay] [data-testid=empty-graph--no-queries]';
const TMViewNoResults = '[data-testid=overlay] [data-testid=empty-graph--no-results]';
const TMResizerHandle = '[data-testid=overlay] [data-testid=draggable-resizer--handle]';
const viewRawDataToggle = '[data-testid=overlay] .view-raw-data-toggle';
const TMAutorefreshDropdown = '[data-testid=overlay] [data-testid=time-machine--bottom] .autorefresh-dropdown [data-testid=dropdown--button]';
const TMAutorefreshDropdownPaused = '[data-testid=overlay] [data-testid=time-machine--bottom] .autorefresh-dropdown [data-testid=dropdown--button] [data-testid=\'icon\'].pause';
const TMAutorefreshDropdownSelected = '[data-testid=overlay] [data-testid=time-machine--bottom] .autorefresh-dropdown [data-testid=dropdown--button] .cf-dropdown--selected';
const TMAutorefreshItem = '//*[@data-testid=\'dropdown-item\'][./*[text()=\'%ITEM%\']]';
const TMAutorefreshForceButton = '[class=time-machine] [class^=autorefresh-dropdown] [data-testid=square-button]';
const TMTimeRangeDropdown = '[data-testid=overlay] [data-testid=time-machine--bottom] [data-testid=timerange-dropdown]';
const TMTimeRangeDropdownItem = '[data-testid=dropdown-item-%ITEM%]';
const TMTimeRangeDropdownContents = '[data-testid=dropdown-menu--contents]';
const switchToScriptEditor = '[data-testid=overlay] [data-testid=time-machine--bottom] [data-testid=switch-to-script-editor] ';
const TMSwitchToQueryBuilder = '[data-testid=switch-query-builder-confirm--button]';
const TMSwitchToQBuilderConfirm = '[data-testid=switch-query-builder-confirm--confirm-button]';
const TMSwitchToQBuilderWarn = '[data-testid=switch-query-builder-confirm--popover--contents]';
const timemachineSubmit = '[data-testid=time-machine-submit-button] ';
const TMQueryTabByName = '//*[contains(@class,\'query-tab \')][./*[@title=\'%NAME%\']]';
const TMQueryBuilder = '[data-testid=query-builder]';
//const functionSelect = '[data-testid=function-selector]';
//const bucketSelect = '[data-testid=bucket-selector]';
const TMBucketSelectorBucket = '[data-testid=bucket-selector] [data-testid=\'selector-list %NAME%\']';
const TMBucketSelectorFilter = '[data-testid=bucket-selector] [data-testid=\'input-field\']';
const TMBuilderCards = '[data-testid=builder-card]';
const bucketSelectItem = '[data-testid=bucket-selector] [data-testid=\'selector-list %ITEM%\'] ';
const bucketSelectSearch = '[data-testid=bucket-selector] [data-testid=builder-card--menu] [class *= search]';
const scriptEditorCodeMirror = '.CodeMirror';
//const scriptMonacoEditor = '.monaco-editor';
const scriptMonacoEditor = '.inputarea';
const TMFluxEditor = '[data-testid=flux-editor]';
const graphCanvas = '[data-testid=\'overlay\'] canvas[data-testid^=giraffe-layer]';
const graphCanvasAxes = '[data-testid=\'overlay\'] canvas[data-testid=giraffe-axes]';
const viewOptionsContainer = '.view-options';
const TMEmptyGraphErrMessage = '.empty-graph-error pre';
const TMDownloadCSV = '[data-testid=button][title*=\'CSV\']';
//Query builder
const TMQBSelectedBucket = '[data-testid=bucket-selector] [data-testid^=\'selector-list\'][class*=selected]';
const TMQBSelectedTagOfCard = '//*[@data-testid=\'builder-card\'][.//*[@data-testid=\'tag-selector--container %INDEX%\']]//*[contains(@data-testid,\'selector-list\')][contains(@class,\'selected\')]';
const TMBuilderCardMenuDurationInput = '[data-testid=\'builder-card--menu\'] [data-testid=\'duration-input\']';
const TMBuilderCardMenuFunctionListItem = '[data-testid=\'function-selector\'] [data-testid=\'selector-list %ITEM%\']';
const TMBuilderCardMenuFunctionFilter = '[data-testid=\'input-field\'][placeholder*=\'functions\']';
const TMBuilderCardMenuFunctionListItems = '[data-testid=function-selector] [data-testid^=\'selector-list\']';
const TMQBSelectedFunctionsByName = '[data-testid=function-selector] [data-testid=\'selector-list %NAME%\'].selected';
const TMQBDurationSuggestions = '[data-testid=\'builder-card--menu\'] [data-testid=\'dropdown-menu--contents\'] [data-testid=\'dropdown-item\']';
const TMQBDurationSuggestionByName = '//*[@data-testid=\'builder-card--menu\']//*[@data-testid=\'dropdown-menu--contents\']//*[@data-testid=\'dropdown-item\'][./*[text()=\'%NAME%\']]';
const TMBuilderTabsAddQuery = '[data-testid=overlay] [class=time-machine-queries--tabs] [data-testid=square-button]';
const TMQBActiveQueryTab = '.query-tab__active';
const TMQBQueryTabByName = '//*[contains(@class,\'query-tab\')][./*[text()=\'%NAME%\']]';
const TMQBRightClickItem = '[data-testid=\'right-click--%ITEM%-tab\']';
const TMQBQueryTabNameInput = 'div.cf-input__focused input';
const TMQBQueryTabs = '.time-machine-queries .query-tab';
//Query Editor
const TMQEFunctionCategory = '//*[@class=\'flux-toolbar--heading\'][text()=\'%NAME%\']';
const TMQEFunctionListItem = '[data-testid=\'flux--%NAME%\']';
const TMQEFunctionListItemInjector = '[data-testid=\'flux--%NAME%--inject\']';
const TMQEFunctionFilter = '.flux-toolbar--search [data-testid=\'input-field\']';
const TMQEFunctionPopup = '[data-testid=\'toolbar-popover--contents\']';
const TMQEFunctionPopupDescription = '[data-testid=\'toolbar-popover--contents\'] .flux-functions-toolbar--description span';
const TMQEFunctionPopupSnippet = '//*[@data-testid=\'toolbar-popover--contents\']//*[@class=\'flux-function-docs--heading\'][text()=\'Example\']/../*[@class=\'flux-function-docs--snippet\']';
const TMQEVariablesTab = '[data-testid=toolbar-tab][title=\'Variables\']';
const TMQEVariablesLabel = '[data-testid=\'variable-name--%LABEL%\']';
const TMQEVariablesInject = '[data-testid=\'variable--%VAR%--inject\']';
const TMQEVariablesFilter = '[data-testid=input-field][placeholder^=\'Filter Variables\']';
const TMQEVariablesPopover = '[data-testid=toolbar-popover--dialog]';
const TMQEVariablesPopoverContents = '[data-testid=\'toolbar-popover--contents\']';
const TMQEVariablePopoverDropdown = '[data-testid=\'variable--tooltip-dropdown\'] [data-testid=\'dropdown--button\'] .caret-down';
//RawData
const TMRawDataTable = '[data-testid=raw-data-table]';
const TMRawDataToggle = '[data-testid=raw-data--toggle]';
const TMRawDataReactGrid = '[data-testid=raw-data-table] [class=ReactVirtualized__Grid__innerScrollContainer]';
const TMRawDataCells = '[class=\'raw-flux-data-table--cell\']';
const TMRawDataCellByIndex = '[data-testid=raw-data-table] .raw-flux-data-table--cell:nth-of-type(%INDEX%)';
const TMRawDataScrollTrackH = '[data-testid=raw-data-table] [class=fancy-scroll--track-h]';
const TMRawDataScrollHThumb = '[data-testid=raw-data-table] [class=fancy-scroll--thumb-h]';
const TMRawDataScrollTrackV = '[data-testid=raw-data-table] [class=fancy-scroll--track-v]';
const TMRawDataScrollVThumb = '[data-testid=raw-data-table] [class^=fancy-scroll--thumb-v]';
const urlCtx = 'cells';
class cellEditOverlay extends influxPage {
constructor(driver) {
super(driver);
}
async isLoaded(){
await super.isLoaded([{type: 'css', selector: cellTitle},
{type: 'css', selector: viewTypeDropdown},
{type: 'css', selector: customizeButton},
{type: 'css', selector: editCancel},
{type: 'css', selector: saveCell},
{type: 'css', selector: TMResizerHandle},
{type: 'css', selector: viewRawDataToggle},
{type: 'css', selector: TMAutorefreshDropdown},
{type: 'css', selector: TMTimeRangeDropdown},
// {type: 'css', selector: switchToScriptEditor},
{type: 'css', selector: timemachineSubmit},
//{type: 'css', selector: queryBuilder},
// {type: 'css', selector: functionSelect},
//{type: 'css', selector: bucketSelect}
], urlCtx);
}
static getTimeMachineOverlay(){
return {type: 'css', selector: timeMachineOverlay};
}
async getTMTop(){
return await this.driver.findElement(By.css(TMTop));
}
async getTMBottom(){
return await this.driver.findElement(By.css(TMBottom));
}
async getCellTitle(){
return await this.driver.findElement(By.css(cellTitle));
}
async getCellNameInput(){
return await this.driver.findElement(By.css(cellNameInput));
}
async getBucketSelectItem(item){
return await this.driver.findElement(By.css(bucketSelectItem.replace('%ITEM%', item)));
}
async getBucketSelectSearch(){
return await this.driver.findElement(By.css(bucketSelectSearch));
}
static getBucketSelectSearchSelector(){
return { type: 'css', selector: bucketSelectSearch };
}
async getEditCancel(){
return await this.driver.findElement(By.css(editCancel));
}
async getSaveCell(){
return await this.driver.findElement(By.css(saveCell));
}
async getTMTimeRangeDropdown(){
return await this.driver.findElement(By.css(TMTimeRangeDropdown));
}
async getTMTimeRangeDropdownItem(item){
//console.log("DEBUG selector " + TMTimeRangeDropdownItem.replace('%ITEM%', item));
return await this.driver.findElement(By.css(TMTimeRangeDropdownItem.replace('%ITEM%', item)));
}
async getTimemachineSubmit(){
return await this.driver.findElement(By.css(timemachineSubmit));
}
async getSwitchToScriptEditor(){
return await this.driver.findElement(By.css(switchToScriptEditor));
}
async getScriptEditorCodeMirror(){
return await this.driver.findElement(By.css(scriptEditorCodeMirror));
}
async getScriptMonacoEditor(){
return await this.driver.findElement(By.css(scriptMonacoEditor));
}
async getGraphCanvas(){
return await this.driver.findElement(By.css(graphCanvas));
}
static getGraphCanvasSelector(){
return { type: 'css', selector: graphCanvas };
}
async getGraphCanvasAxes(){
return await this.driver.findElement(By.css(graphCanvasAxes));
}
static getGraphCanvasAxesSelector(){
return { type: 'css', selector: graphCanvasAxes };
}
async getViewTypeDropdown(){
return await this.driver.findElement(By.css(viewTypeDropdown));
}
async getViewTypeItem(item){
return await this.driver.findElement(By.css(viewTypeItem.replace('%ITEM%', item)));
}
async getViewTypeListContents(){
return await this.driver.findElement(By.css(viewTypeListContents));
}
static getViewTypeListContentsSelector(){
return { type: 'css', selector: viewTypeListContents };
}
async getCustomizeButton(){
return await this.driver.findElement(By.css(customizeButton));
}
async getViewOptionsContainer(){
return await this.driver.findElement(By.css(viewOptionsContainer));
}
static getViewOptionsContainerSelector(){
return { type: 'css', selector: viewOptionsContainer };
}
async getTMViewEmptyGraphQueries(){
return await this.driver.findElement(By.css(TMViewEmptyGraphQueries));
}
async getTMViewNoResults(){
return await this.driver.findElement(By.css(TMViewNoResults));
}
async getTMAutorefreshDropdown(){
return await this.driver.findElement(By.css(TMAutorefreshDropdown));
}
async getTMAutorefreshDropdownSelected(){
return await this.driver.findElement(By.css(TMAutorefreshDropdownSelected))
}
async getTMAutorefreshDropdownPaused(){
return await this.driver.findElement(By.css(TMAutorefreshDropdownPaused));
}
async getTMAutorefreshItem(item){
return await this.driver.findElement(By.xpath(TMAutorefreshItem.replace('%ITEM%', item)));
}
async getTMAutorefreshForceButton(){
return await this.driver.findElement(By.css(TMAutorefreshForceButton));
}
static getTMAutorefreshForceButtonSelector(){
return { type: 'css', selector: TMAutorefreshForceButton };
}
async getTMTimeRangeDropdownContents(){
return await this.driver.findElement(By.css(TMTimeRangeDropdownContents));
}
static getTMTimeRangeDropdownContentsSelector(){
return { type: 'css', selector: TMTimeRangeDropdownContents };
}
async getTMQueryTabByName(name){
return await this.driver.findElement(By.xpath(TMQueryTabByName.replace('%NAME%',name)));
}
async getTMQueryBuilder(){
return await this.driver.findElement(By.css(TMQueryBuilder));
}
async getTMFluxEditor(){
return await this.driver.findElement(By.css(TMFluxEditor));
}
async getTMDownloadCSV(){
return await this.driver.findElement(By.css(TMDownloadCSV));
}
static getTMFluxEditorSelector(){
return { type: 'css', selector: TMFluxEditor };
}
async getTMSwitchToQueryBuilder(){
return await this.driver.findElement(By.css(TMSwitchToQueryBuilder));
}
async getTMSwitchToQBuilderConfirm(){
return await this.driver.findElement(By.css(TMSwitchToQBuilderConfirm));
}
static getTMSwitchToQBuilderWarnSelector(){
return { type: 'css', selector: TMSwitchToQBuilderWarn };
}
async getTMSwitchToQBuilderWarn(){
return await this.driver.findElement(By.css(TMSwitchToQBuilderWarn));
}
async getTMBucketSelectorBucket(name){
return await this.driver.findElement(By.css(TMBucketSelectorBucket.replace('%NAME%', name)));
}
static getTMBucketSelectorBucketSelector(name){
return { type: 'css', selector: TMBucketSelectorBucket.replace('%NAME%', name) };
}
async getTMBucketSelectorFilter(){
return await this.driver.findElement(By.css(TMBucketSelectorFilter));
}
async getTMBuilderCards(){
return await this.driver.findElements(By.css(TMBuilderCards));
}
async getTMBuilderCardByIndex(index){
return (await this.driver.findElements(By.css(TMBuilderCards)))[index - 1];
}
async getTMBuilderCardMenuDurationInput(){
return await this.driver.findElement(By.css(TMBuilderCardMenuDurationInput));
}
async getTMBuilderCardMenuFunctionListItem(item){
return await this.driver.findElement(By.css(TMBuilderCardMenuFunctionListItem.replace('%ITEM%', item)));
}
static getTMBuilderCardMenuFunctionListItemSelector(item){
return { type: 'css', selector: TMBuilderCardMenuFunctionListItem.replace('%ITEM%', item) };
}
async getTMBuilderCardMenuFunctionFilter(){
return await this.driver.findElement(By.css(TMBuilderCardMenuFunctionFilter));
}
async getTMBuilderCardMenuFunctionListItems(){
return await this.driver.findElements(By.css(TMBuilderCardMenuFunctionListItems));
}
async getTMQBDurationSuggestions(){
return await this.driver.findElements(By.css(TMQBDurationSuggestions));
}
async getTMQBDurationSuggestionByName(name){
return await this.driver.findElement(By.xpath(TMQBDurationSuggestionByName.replace('%NAME%', name)));
}
async getTMResizerHandle(){
return await this.driver.findElement(By.css(TMResizerHandle));
}
async getTMBuilderTabsAddQuery(){
return await this.driver.findElement(By.css(TMBuilderTabsAddQuery));
}
async getTMQBSelectedBucket(){
return await this.driver.findElement(By.css(TMQBSelectedBucket));
}
async getTMQBSelectedTagOfCard(index){
return await this.driver.findElement(By.xpath(TMQBSelectedTagOfCard.replace('%INDEX%', index)));
}
async getTMQBActiveQueryTab(){
return await this.driver.findElement(By.css(TMQBActiveQueryTab));
}
async getTMQBQueryTabByName(name){
return await this.driver.findElement(By.xpath(TMQBQueryTabByName.replace('%NAME%', name)));
}
static getTMQBQueryTabSelectorByName(name){
return { type: 'xpath', selector: TMQBQueryTabByName.replace('%NAME%', name)};
}
async getTMQBSelectedFunctionByName(name){
return await this.driver.findElements(By.css(TMQBSelectedFunctionsByName.replace('%NAME%', name)));
}
async getTMQBRightClickItem(item){
return await this.driver.findElement(By.css(TMQBRightClickItem.replace('%ITEM%', item.toLowerCase().trim())));
}
async getTMQBQueryTabNameInput(){
return await this.driver.findElement(By.css(TMQBQueryTabNameInput));
}
async getTMQBQueryTabs(){
return await this.driver.findElements(By.css(TMQBQueryTabs));
}
async getTMEmptyGraphErrMessage(){
return await this.driver.findElement(By.css(TMEmptyGraphErrMessage));
}
async getTMQEFunctionCategory(name){
return await this.driver.findElement(By.xpath(TMQEFunctionCategory.replace('%NAME%', name)));
}
async getTMQEFunctionListItem(name){
return await this.driver.findElement(By.css(TMQEFunctionListItem.replace('%NAME%', name)));
}
static getTMQEFunctionListItemSelector(name){
return { type: 'css', selector: TMQEFunctionListItem.replace('%NAME%', name) };
}
async getTMQEFunctionListItemInjector(name){
return await this.driver.findElement(By.css(TMQEFunctionListItemInjector.replace('%NAME%',name)));
}
async getTMQEFunctionFilter(){
return await this.driver.findElement(By.css(TMQEFunctionFilter));
}
async getTMQEFunctionPopupDescription(){
return await this.driver.findElement(By.css(TMQEFunctionPopupDescription));
}
async getTMQEFunctionPopupSnippet(){
return await this.driver.findElement(By.xpath(TMQEFunctionPopupSnippet));
}
static getTMQEFunctionPopupSelector(){
return { type: 'css', selector: TMQEFunctionPopup };
}
async getTMRawDataTable(){
return await this.driver.findElement(By.css(TMRawDataTable));
}
static getTMRawDataTableSelector(){
return { type: 'css', selector: TMRawDataTable}
}
async getTMRawDataToggle(){
return await this.driver.findElement(By.css(TMRawDataToggle));
}
async getTMRawDataCells(){
return await this.driver.findElements(By.css(TMRawDataCells));
}
async getTMRawDataReactGrid(){
return await this.driver.findElement(By.css(TMRawDataReactGrid));
}
async getTMRawDataScrollTrackH(){
return await this.driver.findElement(By.css(TMRawDataScrollTrackH));
}
async getTMRawDataScrollHThumb(){
return await this.driver.findElement(By.css(TMRawDataScrollHThumb));
}
async getTMRawDataCellByIndex(index){
return await this.driver.findElement(By.css(TMRawDataCellByIndex.replace('%INDEX%',index)));
}
async getTMRawDataScrollTrackV(){
return await this.driver.findElement(By.css(TMRawDataScrollTrackV));
}
async getTMRawDataScrollVThumb(){
return await this.driver.findElement(By.css(TMRawDataScrollVThumb));
}
async getTMQEVariablesTab(){
return await this.driver.findElement(By.css(TMQEVariablesTab));
}
async getTMQEVariablesLabel(label){
return await this.driver.findElement(By.css(TMQEVariablesLabel.replace('%LABEL%', label)));
}
static getTMQEVariablesLabelSelector(label){
return { type: 'css', selector: TMQEVariablesLabel.replace('%LABEL%', label) }
}
async getTMQEVariablesInject(variable){
return await this.driver.findElement(By.css(TMQEVariablesInject.replace('%VAR%', variable)));
}
async getTMQEVariablesFilter(){
return await this.driver.findElement(By.css(TMQEVariablesFilter));
}
async getTMQEVariablesPopover(){
return await this.driver.findElement(By.css(TMQEVariablesPopover));
}
static getTMQEVariablesPopoverSelector(){
return { type: 'css', selector: TMQEVariablesPopover };
}
async getTMQEVariablePopoverDropdown(){
return await this.driver.findElement(By.css(TMQEVariablePopoverDropdown));
}
async getTMQEVariablesPopoverContents(){
return await this.driver.findElement(By.css(TMQEVariablesPopoverContents));
}
}
module.exports = cellEditOverlay;

View File

@ -0,0 +1,311 @@
const influxPage = require(__srcdir + '/pages/influxPage.js');
const { By } = require('selenium-webdriver');
const pageTitle = '[data-testid=page-title]';
const nameInput = '[data-testid=page-header] [data-testid=input-field]';
//const graphToolTips = '[data-testid=page-header--right] [data-testid=graphtips-question-mark]';
const graphToolTips = '[data-testid=page-control-bar] [data-testid=graphtips-question-mark]';
//const addCellButtonHeader = '//*[@data-testid=\'page-header--right\']//*[@data-testid=\'button\'][.//*[text()=\'Add Cell\']]';
const addCellButtonHeader = '//*[@data-testid=\'page-control-bar--left\']//*[@data-testid=\'button\'][.//*[text()=\'Add Cell\']]';
//const addNoteButton = '//*[@data-testid=\'page-header--right\']//*[@data-testid=\'button\'][.//*[text()=\'Add Note\']]';
const addNoteButton = '//*[@data-testid=\'page-control-bar--left\']//*[@data-testid=\'button\'][.//*[text()=\'Add Note\']]';
//const variablesButton = '//*[@data-testid=\'page-header--right\']//*[@data-testid=\'button\'][.//*[text()=\'Variables\']]';
const variablesButton = '//*[@data-testid=\'page-control-bar--left\']//*[@data-testid=\'button\'][.//*[text()=\'Variables\']]';
//const timeLocaleDropdown = '//*[@data-testid=\'page-header--right\']//*[@data-testid=\'dropdown--button\'][.//*[contains(@class,\'annotate\')]]';
const timeLocaleDropdown = '//*[@data-testid=\'page-control-bar--right\']//*[@data-testid=\'dropdown--button\'][.//*[contains(@class,\'annotate\')]]';
//const autorefresh = '//*[@data-testid=\'page-header--right\']/*[contains(@class,\'autorefresh-dropdown\')]';
const autorefresh = '//*[@data-testid=\'page-control-bar--right\']/*[contains(@class,\'autorefresh-dropdown\')]';
//const refreshRateDropdown = '//*[@data-testid=\'page-header--right\']//*[contains(@class,\'autorefresh\')]//*[@data-testid=\'dropdown--button\']';
const refreshRateDropdown = '//*[@data-testid=\'page-control-bar--right\']//*[contains(@class,\'autorefresh\')]//*[@data-testid=\'dropdown--button\']';
//const forceRefreshButton = '//*[@data-testid=\'page-header--right\']//*[contains(@class,\'autorefresh\')]//*[@data-testid=\'square-button\']';
const forceRefreshButton = '//*[@data-testid=\'page-control-bar--right\']//*[contains(@class,\'autorefresh\')]//*[@data-testid=\'square-button\']';
//const timeRangeDropdown = '//*[@data-testid=\'page-header--right\']//*[@data-testid=\'timerange-dropdown\']//*[@data-testid=\'dropdown--button\']';
const timeRangeDropdown = '//*[@data-testid=\'page-control-bar--right\']//*[@data-testid=\'timerange-dropdown\']//*[@data-testid=\'dropdown--button\']';
const timeRangeDropdownSelected = '[data-testid=\'timerange-dropdown\'] [class*=selected]';
const timeRangeDropdownItem = '[data-testid=dropdown-item-past%ITEM%]';
//const presentationModeButton = '//*[@data-testid=\'page-header--right\']//*[contains(@title,\'Presentation\')]';
const presentationModeButton = '[data-testid=\'presentation-mode-toggle\']';
const variableValueDropdownButtonForVar = '//*[@class=\'variable-dropdown\'][.//*[text()=\'%VARNAME%\']]//*[@data-testid=\'variable-dropdown--button\']';
const variableValueDropdownItem = '//*[@class=\'variable-dropdown\'][.//*[text()=\'%VARNAME%\']]//*[@data-testid=\'variable-dropdown\']//*[@data-testid=\'variable-dropdown--item\'][.//*[text()=\'%ITEM%\']]'
const dropdownMenuItem = '//*[@data-testid=\'dropdown-menu\']//*[contains(@data-testid,\'dropdown-item\')]/*[text()=\'%ITEM%\']';
const dropdownMenuDivider = '//*[@data-testid=\'dropdown-menu\']//*[@data-testid=\'dropdown-divider\'][text()=\'%LABEL%\']';
const emptyStateTextLink = '[data-testid=\'empty-state--text\'] a';
const emptyStateAddCellButton = '[data-testid=\'empty-state\'] [data-testid=\'add-cell--button\']';
const cellByName = '//*[contains(@class, \' cell \')][.//*[text()=\'%NAME%\']]';
const cellEmptyGraphMessage = '//*[contains(@class, \' cell \')][.//*[text()=\'%NAME%\']]//*[@data-testid=\'empty-graph--no-queries\']';
const cellEmptyGraphNoResults = '//*[contains(@class, \' cell \')][.//*[text()=\'%NAME%\']]//*[@data-testid=\'empty-graph--no-results\']';
const cellEmptyGraphError = '//*[contains(@class, \' cell \')][.//*[text()=\'%NAME%\']]//*[@data-testid=\'empty-graph--error\']';
const cellEmptyGraphErrorIcon = '//*[contains(@class, \' cell \')][.//*[text()=\'%NAME%\']]//*[@data-testid=\'empty-graph--error\']//*[contains(@class,\'empty-graph-error--icon\')]';
const emptyGraphPopoverContents = '[data-testid^=emptygraph-popover--contents]';
const cellTitle = '//*[@class=\'cell--header\'][./*[text()=\'%NAME%\']]';
const cellHandleByName = '//*[contains(@class, \' cell \')][.//*[text()=\'%NAME%\']]//*[@class=\'cell--draggable\']';
const cellResizerByName = '//*[contains(@class, \' cell \')][.//*[text()=\'%NAME%\']]//*[@class=\'react-resizable-handle\']';
const cellContextToggleByName = '//*[contains(@class, \' cell \')][.//*[text()=\'%NAME%\']]//*[@data-testid=\'cell-context--toggle\']';
const cellNoteByName = '//*[contains(@class, \' cell \')][.//*[text()=\'%NAME%\']]//*[@class=\'cell--note-indicator\']';
const cellPopoverContents = '[data-testid=popover--contents]';
const cellPopoverContentsConfigure = '[data-testid=popover--contents] [data-testid=\'cell-context--configure\']';
const cellPopoverContentsAddNote = '[data-testid=popover--contents] [data-testid=\'cell-context--note\']';
const cellPopoverContentsClone = '[data-testid=popover--contents] [data-testid=\'cell-context--clone\']';
const cellPopoverContentsDelete = '[data-testid=popover--contents] [data-testid=\'cell-context--delete\']';
const cellPopoverContentsDeleteConfirm = '[data-testid=popover--contents] [data-testid=\'cell-context--delete-confirm\']';
const cellPopoverContentsEditNote = '[data-testid=cell-context--note]';
const cellCanvasLine = '//*[contains(@class, \' cell \')][.//*[text()=\'%NAME%\']]//*[@data-testid=\'giraffe-layer-line\']';
const cellCanvasAxes = '//*[contains(@class, \' cell \')][.//*[text()=\'%NAME%\']]//*[@data-testid=\'giraffe-axes\']';
const cellHoverBox = '[data-testid=giraffe-layer-hover-line]';
const notePopupCodeMirror = '[data-testid=overlay--body] .CodeMirror';
const notePopupNoDataToggle = '[data-testid=overlay--body] [data-testid=slide-toggle]';
const notePopupEditorPreview = '[data-testid=overlay--body] .note-editor--preview';
const notePopupEditorPreviewTag = '[data-testid=overlay--body] .note-editor--preview %TAG%';
const notePopupEditorPreviewText = '[data-testid=overlay--body] .note-editor--preview .markdown-format';
const notePopupGuideLink = '[href*=\'markdownguide.org\']';
const notePopover = '[data-testid=popover--dialog]';
const notePopoverContents = '[data-testid=popover--dialog] .markdown-format';
const noteCellMarkdownTag = '[data-testid=cell--view-empty] .markdown-format %TAG%';
const noteCellContextEdit = '[data-testid=cell-context--note]';
const urlCtx = 'dashboards';
class dashboardPage extends influxPage {
constructor(driver) {
super(driver);
}
async isLoaded(){
await super.isLoaded([{type: 'css', selector: pageTitle},
{type: 'css', selector: graphToolTips},
{type: 'xpath', selector: addCellButtonHeader},
{type: 'xpath', selector: addNoteButton},
{type: 'xpath', selector: variablesButton},
{type: 'xpath', selector: timeLocaleDropdown},
{type: 'xpath', selector: autorefresh},
{type: 'xpath', selector: timeRangeDropdown},
{type: 'css', selector: presentationModeButton}], urlCtx);
}
async getPageTitle(){
//return await this.driver.findElement(By.css(pageTitle));
return await this.smartGetElement({type: 'css', selector: pageTitle});
}
async getNameInput(){
return await this.driver.findElement(By.css(nameInput));
}
async getCellByName(name){
return await this.driver.findElement(By.xpath(cellByName.replace('%NAME%', name)));
}
static getCellSelectorByName(name){
return {type: 'xpath', selector: cellByName.replace('%NAME%', name) };
}
async getCellsByName(name){
return await this.driver.findElements(By.xpath(cellByName.replace('%NAME%', name)));
}
async getGraphToolTips(){
return await this.driver.findElement(By.css(graphToolTips));
}
async getAddCellButtonHeader(){
return await this.driver.findElement(By.xpath(addCellButtonHeader));
}
async getAddNoteButton(){
return await this.driver.findElement(By.xpath(addNoteButton));
}
async getVariablesButton(){
return await this.driver.findElement(By.xpath(variablesButton));
}
async getTimeLocaleDropdown(){
return await this.driver.findElement(By.xpath(timeLocaleDropdown));
}
async getRefreshRateDropdown(){
return await this.driver.findElement(By.xpath(refreshRateDropdown));
}
async getForceRefreshButton(){
return await this.driver.findElement(By.xpath(forceRefreshButton));
}
async getTimeRangeDropdown(){
return await this.driver.findElement(By.xpath(timeRangeDropdown));
}
async getTimeRangeDropdownSelected(){
return await this.driver.findElement(By.css(timeRangeDropdownSelected));
}
async getTimeRangeDropdownItem(item){
return await this.driver.findElement(By.css(timeRangeDropdownItem.replace('%ITEM%', item)));
}
async getPresentationModeButton(){
return await this.driver.findElement(By.css(presentationModeButton));
}
async getEmptyStateTextLink(){
return await this.driver.findElement(By.css(emptyStateTextLink));
}
async getEmptyStateAddCellButton(){
return await this.driver.findElement(By.css(emptyStateAddCellButton));
}
async getDropdownMenuItem(item){
return await this.driver.findElement(By.xpath(dropdownMenuItem.replace('%ITEM%', item)));
}
async getdropdownMenuDivider(label){
return await this.driver.findElement(By.xpath(dropdownMenuDivider.replace('%LABEL%', label)));
}
async getCellEmptyGraphMessage(name){
return await this.driver.findElement(By.xpath(cellEmptyGraphMessage.replace('%NAME%', name)));
}
async getCellEmptyGraphNoResults(name){
return await this.driver.findElement(By.xpath(cellEmptyGraphNoResults.replace('%NAME%', name)));
}
async getCellEmptyGraphError(name){
return await this.driver.findElement(By.xpath(cellEmptyGraphError.replace('%NAME%', name)));
}
async getCellTitle(name){
return await this.driver.findElement(By.xpath(cellTitle.replace('%NAME%', name)));
}
async getCellHandleByName(name){
return await this.driver.findElement(By.xpath(cellHandleByName.replace('%NAME%', name)));
}
async getCellResizerByName(name){
return await this.driver.findElement(By.xpath(cellResizerByName.replace('%NAME%', name)));
}
static getCellPopoverContentsSelector(){
return { type: 'css', selector: cellPopoverContents};
}
async getCellContextToggleByName(name){
return await this.driver.findElement(By.xpath(cellContextToggleByName.replace('%NAME%', name)));
}
async getCellPopoverContentsConfigure(){
return await this.driver.findElement(By.css(cellPopoverContentsConfigure));
}
async getCellPopoverContentsAddNote(){
return await this.driver.findElement(By.css(cellPopoverContentsAddNote));
}
async getCellPopoverContentsClone(){
return await this.driver.findElement(By.css(cellPopoverContentsClone));
}
async getCellPopoverContentsDelete(){
return await this.driver.findElement(By.css(cellPopoverContentsDelete));
}
async getCellPopoverContentsDeleteConfirm(){
return await this.driver.findElement(By.css(cellPopoverContentsDeleteConfirm));
}
async getcellPopoverContentsEditNote(){
return await this.driver.findElement(By.css(cellPopoverContentsEditNote));
}
async getNotePopupCodeMirror(){
return await this.driver.findElement(By.css(notePopupCodeMirror));
}
async getNotePopupNoDataToggle(){
return await this.driver.findElement(By.css(notePopupNoDataToggle));
}
async getNotePopupEditorPreview(){
return await this.driver.findElement(By.css(notePopupEditorPreview));
}
async getNotePopupEditorPreviewText(){
return await this.driver.findElement(By.css(notePopupEditorPreviewText));
}
async getNotePopupGuideLink(){
return await this.driver.findElement(By.css(notePopupGuideLink));
}
async getCellNoteByName(name){
return await this.driver.findElement(By.xpath(cellNoteByName.replace('%NAME%', name)));
}
async getNotePopoverContents(){
return await this.driver.findElement(By.css(notePopoverContents));
}
async getNotePopover(){
return await this.driver.findElement(By.css(notePopover));
}
static getNotePopoverSelector(){
return { type: 'css', selector: notePopover};
}
async getNotePopupEditorPreviewTag(tag){
return await this.driver.findElement(By.css(notePopupEditorPreviewTag.replace('%TAG%', tag)));
}
async getCellCanvasLine(name){
return await this.driver.findElement(By.xpath(cellCanvasLine.replace('%NAME%', name)));
}
async getCellCanvasAxes(name){
return await this.driver.findElement(By.xpath(cellCanvasAxes.replace('%NAME%', name)));
}
async getCellHoverBox(){
return await this.driver.findElement(By.css(cellHoverBox));
}
async getEmptyGraphPopoverContents(){
return await this.driver.findElement(By.css(emptyGraphPopoverContents));
}
async getCellEmptyGraphErrorIcon(name){
return await this.driver.findElement(By.xpath(cellEmptyGraphErrorIcon.replace('%NAME%', name)));
}
async getVariableValueDropdownButtonForVar(varname){
return await this.driver.findElement(By.xpath(variableValueDropdownButtonForVar.replace('%VARNAME%', varname)));
}
static getVariableValueDropdownButtonForVarSelector(varname){
return {type: 'xpath', selector: variableValueDropdownButtonForVar.replace('%VARNAME%', varname)};
}
async getVariableValueDropdownItem(varname,item){
return await this.driver.findElement(By.xpath(variableValueDropdownItem
.replace("%VARNAME%", varname)
.replace("%ITEM%", item)));
}
async getNoteCellMarkdownTag(tag){
return await this.driver.findElement(By.css(noteCellMarkdownTag.replace('%TAG%', tag)));
}
}
module.exports = dashboardPage;

View File

@ -0,0 +1,332 @@
const influxPage = require(__srcdir + '/pages/influxPage.js');
const { By } = require('selenium-webdriver');
const createDashboardDropdown = '[data-testid=add-resource-dropdown--button]';
const filterDashboards = '[data-testid=search-widget]';
const sortTypeButton = '[data-testid=resource-sorter--button]:nth-of-type(1)';
const sortTypeItem = '[data-testid=\'resource-sorter--%ITEM%\']';
const modifiedSortButton = '[data-testid=resource-list--sorter]:nth-of-type(2)';
const createDashboardDropdownEmpty = '[data-testid=\'page-contents\'] [data-testid=\'add-resource-dropdown--button\']';
const createDashboardItems = '[data-testid^=add-resource-dropdown--][id]';
const dashboardCardByName = '//*[@data-testid=\'dashboard-card\'][.//span[text() = \'%NAME%\']]';
const dashboardCardExportButton = '//*[@data-testid=\'dashboard-card\'][.//span[text() = \'%NAME%\']]//*[@class=\'context-menu--container\'][.//*[text() = \'Export\']]';
const dashboardCardExportConfirm = '//*[@data-testid=\'dashboard-card\'][.//span[text() = \'%NAME%\']]//*[text()=\'Export\']';
const dashboardCardCloneButton = '//*[@data-testid=\'dashboard-card\'][.//span[text() = \'%NAME%\']]//*[@class=\'context-menu--container\'][.//*[text() = \'Clone\']]';
const dashboardCardCloneConfirm = '//*[@data-testid=\'dashboard-card\'][.//span[text() = \'%NAME%\']]//*[@class=\'context-menu--container\']//*[text() = \'Clone\']';
const dashboardCardDeleteButton = '//*[@data-testid=\'dashboard-card\'][.//*[text()=\'%NAME%\']]//*[@data-testid=\'context-delete-menu\']';
const dashboardCardDeleteConfirm = '//*[@data-testid=\'dashboard-card\'][.//*[text()=\'%NAME%\']]//*[@data-testid=\'context-delete-dashboard\']';
//const dashboardCardName = '//*[@data-testid=\'dashboard-card\'][.//span[text() = \'%NAME%\']]//*[@data-testid=\'dashboard-card--name\']';
const dashboardCardName = '//*[@data-testid=\'dashboard-card\']//span[text() = \'%NAME%\']';
const dashboardCardNames = '[data-testid=\'dashboard-card--name\']';
const dashboardCardNameButton = '//*[@data-testid=\'dashboard-card\'][.//span[text() = \'%NAME%\']]//*[@data-testid=\'dashboard-card--name-button\']';
const dashboardCardNameInput = '//*[@data-testid=\'dashboard-card\'][.//span[text() = \'%NAME%\']]//*[@data-testid=\'dashboard-card--input\']';
const dashboardCardDescription = '//*[@data-testid=\'dashboard-card\'][.//span[text() = \'%NAME%\']]//*[@data-testid=\'resource-list--editable-description\']';
const dashboardCardDescriptionEdit = '//*[@data-testid=\'dashboard-card\'][.//span[text() = \'%NAME%\']]//*[@data-testid=\'resource-list--editable-description\']//*[@data-testid=\'icon\']';
const dashboardCardDescriptionInput = '//*[@data-testid=\'dashboard-card\'][.//span[text() = \'%NAME%\']]//*[@data-testid=\'resource-list--editable-description\']//*[@data-testid=\'input-field\']';
const dashboardCardLabelsEmpty = '//*[@data-testid=\'dashboard-card\'][.//span[text() = \'%NAME%\']]//*[@data-testid=\'inline-labels--empty\']';
const dashboardCardAddLabels = '//*[@data-testid=\'dashboard-card\'][.//span[text() = \'%NAME%\']]//*[@data-testid=\'inline-labels--add\']';
const dashboardCardLabelPill = '//*[@data-testid=\'dashboard-card\'][.//span[text() = \'%NAME%\']]//*[@data-testid=\'label--pill %LABEL%\']';
const dashboardCardLabelPillDelete = '//*[@data-testid=\'dashboard-card\'][.//span[text() = \'%NAME%\']]//*[@data-testid=\'label--pill--delete %LABEL%\']';
////*[@data-testid='dashboard-card'][.//*[text()='Test Dashboard']]//*[@data-testid='context-menu']
const addLabelsPopover = '[data-testid=\'inline-labels--popover\']';
const addLabelsPopoverLabel = '//*[@data-testid=\'inline-labels--popover--contents\']//*[contains(@data-testid,\'label--pill\')][text()=\'%LABEL%\']';
const addLabelsPopoverFilter = '[data-testid=\'inline-labels--popover-field\']';
const addLabelsLabelPills = '[data-testid^=\'label--pill\']';
const addLabelsPopoverListItem = '[data-testid^=\'label-list--item %ITEM%\']';
const addLabelsPopoverNewItem = '[data-testid^=\'inline-labels--create-new\']';
const importPopupUploadFileRadio = '[data-testid=overlay--body] [data-testid=select-group--option][title=\'Upload\']';
const importPopupPasteJSONRadio = '[data-testid=overlay--body] [data-testid=select-group--option][title=\'Paste\']';
const importPopupImportJSONButton = '[data-testid=\'overlay--footer\'] [title^=\'Import JSON\']';
const importPopupDismiss = '[data-testid=\'overlay--header\'] button';
const importPopupFileInput = '[data-testid=\'overlay--body\'] [class*=\'drag-and-drop--form\'] ';
const importPopupFileInputHeader = '[data-testid=\'overlay--body\'] [class*=\'drag-and-drop--header\']';
const importPopupDragNDropFile = 'input[type=file]'; //N.B. has display:none
const importPopupJSONTextarea = '[data-testid=\'overlay--body\'] [data-testid=\'import-overlay--textarea\']';
const fromTemplatePopupDismiss = '[data-testid=\'overlay--header\'] button';
const fromTemplatePopupCancel = '[data-testid=\'overlay--footer\'] [data-testid=\'button\'][title=\'Cancel\']';
const fromTemplatePopupCreateDBoard = '[data-testid=\'create-dashboard-button\']';
const fromTemplatePopupTemplateList = '//*[@data-testid=\'dapper-scrollbars\'][.//*[contains(@data-testid, \'template--\')]]';
const fromTemplatePopupTemplateItem = '[data-testid=\'template--%ITEM%\']';
const fromTemplatePopupTemplatePanel = '[data-testid=\'template-panel\']';
const fromTemplatePopupPreviewCell = '//*[@data-testid=\'template-panel\']//div[h5[text()=\'Cells\']]/p[text()=\'%NAME%\']';
const exportPopupDismiss = '[data-testid=\'overlay--header\'] [type=\'button\'][class*=dismiss]';
const exportPopupCodeMirror = '.CodeMirror';
const exportPopupDownloadJSON = '[data-testid=\'overlay--footer\'] [data-testid=\'button\'][title=\'Download JSON\']';
const exportPopupSaveAsTemplate = '[data-testid=\'overlay--footer\'] [data-testid=\'button\'][title=\'Save as template\']';
const exportPopupCopyToClipboard = '[data-testid=\'overlay--footer\'] [data-testid=\'button-copy\']';
const urlCtx = 'dashboards';
class dashboardsPage extends influxPage {
constructor(driver){
super(driver);
}
async isLoaded(){
await super.isLoaded([{type: 'css', selector: createDashboardDropdown},
{type: 'css', selector: filterDashboards} ,
{type: 'css', selector: sortTypeButton}
// {type: 'css', selector: modifiedSortButton}
], urlCtx);
}
async getCreateDashboardDropdown(){
return await this.driver.findElement(By.css(createDashboardDropdown));
}
async getFilterDashboards(){
return await this.driver.findElement(By.css(filterDashboards));
}
async getSortTypeButton(){
return await this.driver.findElement(By.css(sortTypeButton));
}
async getSortTypeItem(item){
return await this.driver.findElement(By.css(sortTypeItem.replace('%ITEM%', item)));
}
// async getModifiedSortButton(){
// return await this.driver.findElement(By.css(modifiedSortButton));
// }
async getCreateDashboardItem(item){
return await this.driver.findElement(By.css(`[data-testid^=add-resource-dropdown--][id='${item}']`));
}
async getCreateDashboardDropdownEmpty(){
return await this.driver.findElement(By.css(createDashboardDropdownEmpty));
}
static getCreateDashboardDropdownEmptySelector(){
return { type: 'css', selector: createDashboardDropdownEmpty};
}
async getCreateDashboardItems(){
return await this.driver.findElements(By.css(createDashboardItems));
}
async getDashboardCardByName(name){
return await this.driver.findElement(By.xpath(dashboardCardByName.replace('%NAME%', name)));
}
static getDashboardCardSelectorByName(name){
return { type: 'xpath', selector: dashboardCardByName.replace('%NAME%', name) };
}
async getDashboardCardExportButton(name){
return await this.driver.findElement(By.xpath(dashboardCardExportButton.replace('%NAME%', name)));
}
static getDashboardCardExportButtonSelector(name){
return { type: 'xpath', selector: dashboardCardExportButton.replace('%NAME%', name)};
}
async getDashboardCardExportConfirm(name){
return await this.driver.findElement(By.xpath(dashboardCardExportConfirm.replace('%NAME%', name)));
}
async getDashboardCardCloneButton(name){
return await this.driver.findElement(By.xpath(dashboardCardCloneButton.replace('%NAME%', name)));
}
static getDashboardCardCloneButtonSelector(name){
return { type: 'xpath', selector: dashboardCardCloneButton.replace('%NAME%', name)};
}
async getDashboardCardDeleteButton(name){
return await this.driver.findElement(By.xpath(dashboardCardDeleteButton.replace('%NAME%', name)));
}
static getDashboardCardDeleteButtonSelector(name){
return { type: 'xpath', selector: dashboardCardDeleteButton.replace('%NAME%', name)};
}
async getDashboardCardName(name){
return await this.driver.findElement(By.xpath(dashboardCardName.replace('%NAME%', name)));
}
async getDashboardCardNameButton(name){
return await this.driver.findElement(By.xpath(dashboardCardNameButton.replace('%NAME%', name)));
}
async getDashboardCardNameInput(name){
return await this.driver.findElement(By.xpath(dashboardCardNameInput.replace('%NAME%', name)));
}
async getDashboardCardDescription(name){
return await this.driver.findElement(By.xpath(dashboardCardDescription.replace('%NAME%', name)));
}
async getDashboardCardDescriptionEdit(name){
return await this.driver.findElement(By.xpath(dashboardCardDescriptionEdit.replace('%NAME%', name)));
}
async getDashboardCardDescriptionInput(name){
return await this.driver.findElement(By.xpath(dashboardCardDescriptionInput.replace('%NAME%', name)));
}
async getDashboardCardLabelsEmpty(name){
return await this.driver.findElement(By.xpath(dashboardCardLabelsEmpty.replace('%NAME%', name)));
}
async getDashboardCardAddLabels(name){
return await this.driver.findElement(By.xpath(dashboardCardAddLabels.replace('%NAME%', name)));
}
async getAddLabelsPopoverLabel(label){
return await this.driver.findElement(By.xpath(addLabelsPopoverLabel.replace('%LABEL%', label)));
}
static getAddLabelsPopoverLabelSelector(label){
return { type: 'xpath', selector: addLabelsPopoverLabel.replace('%LABEL%', label)};
}
async getAddLabelsPopoverFilter(){
return await this.driver.findElement(By.css(addLabelsPopoverFilter));
}
async getAddLabelsLabelPills(){
return await this.driver.findElements(By.css(addLabelsLabelPills));
}
async getAddLabelsPopoverListItem(item){
return await this.driver.findElement(By.css(addLabelsPopoverListItem.replace('%ITEM%', item)));
}
async getAddLabelsPopoverNewItem(){
return await this.driver.findElement(By.css(addLabelsPopoverNewItem));
}
static getAddLabelsPopoverNewItemSelector(){
return { type: 'css', selector: addLabelsPopoverNewItem };
}
async getDashboardCardLabelPill(name, label){
return await this.driver.findElement(By.xpath(dashboardCardLabelPill
.replace('%NAME%', name).replace('%LABEL%', label)));
}
static getDashboardCardLabelPillSelector(name, label){
return { type: 'xpath', selector: dashboardCardLabelPill
.replace('%NAME%', name).replace('%LABEL%', label) };
}
async getAddLabelsPopover(){
return await this.driver.findElement(By.css(addLabelsPopover));
}
static getAddLabelsPopoverSelector(){
return { type: 'css', selector: addLabelsPopover};
}
async getDashboardCardLabelPillDelete(name, label){
return await this.driver.findElement(By.xpath(dashboardCardLabelPillDelete
.replace('%NAME%', name).replace('%LABEL%', label)));
}
async getImportPopupUploadFileRadio(){
return await this.driver.findElement(By.css(importPopupUploadFileRadio));
}
async getImportPopupPasteJSONRadio(){
return await this.driver.findElement(By.css(importPopupPasteJSONRadio));
}
async getImportPopupImportJSONButton(){
return await this.driver.findElement(By.css(importPopupImportJSONButton));
}
async getImportPopupDismiss(){
return await this.driver.findElement(By.css(importPopupDismiss));
}
async getImportPopupFileInput(){
return await this.driver.findElement(By.css(importPopupFileInput));
}
static getImportPopupFileInputSelector(){
return { type: 'css', selector: importPopupFileInput };
}
async getImportPopupFileInputHeader(){
return await this.driver.findElement(By.css(importPopupFileInputHeader));
}
async getImportPopupDragNDropFile(){
return await this.driver.findElement(By.css(importPopupDragNDropFile));
}
async getImportPopupJSONTextarea(){
return await this.driver.findElement(By.css(importPopupJSONTextarea));
}
async getFromTemplatePopupDismiss(){
return await this.driver.findElement(By.css(fromTemplatePopupDismiss));
}
async getFromTemplatePopupCancel(){
return await this.driver.findElement(By.css(fromTemplatePopupCancel));
}
async getFromTemplatePopupCreateDBoard(){
return await this.driver.findElement(By.css(fromTemplatePopupCreateDBoard));
}
async getFromTemplatePopupTemplateList(){
return await this.driver.findElement(By.xpath(fromTemplatePopupTemplateList));
}
async getFromTemplatePopupTemplateItem(item){
return await this.driver.findElement(By.css(fromTemplatePopupTemplateItem.replace('%ITEM%', item)));
}
async getFromTemplatePopupTemplatePanel(){
return await this.driver.findElement(By.css(fromTemplatePopupTemplatePanel));
}
async getfromTemplatePopupPreviewCell(name){
return await this.driver.findElement(By.xpath(fromTemplatePopupPreviewCell.replace('%NAME%', name)));
}
async getDashboardCardNames(){
return await this.driver.findElements(By.css(dashboardCardNames));
}
async getDashboardCardDeleteConfirm(name){
return await this.driver.findElement(By.xpath(dashboardCardDeleteConfirm.replace('%NAME%', name)));
}
async getDashboardCardCloneConfirm(name){
return await this.driver.findElement(By.xpath(dashboardCardCloneConfirm.replace('%NAME%', name)));
}
async getExportPopupDismiss(){
return await this.driver.findElement(By.css(exportPopupDismiss));
}
async getExportPopupCodeMirror(){
return await this.driver.findElement(By.css(exportPopupCodeMirror));
}
async getExportPopupDownloadJSON(){
return await this.driver.findElement(By.css(exportPopupDownloadJSON));
}
async getExportPopupSaveAsTemplate(){
return await this.driver.findElement(By.css(exportPopupSaveAsTemplate));
}
async getexportPopupCopyToClipboard(){
return await this.driver.findElement(By.css(exportPopupCopyToClipboard));
}
}
module.exports = dashboardsPage;

View File

@ -0,0 +1,83 @@
const influxPage = require(__srcdir + '/pages/influxPage.js');
const { By } = require('selenium-webdriver');
const timeLocaleDropdown = '[data-testid=select-dropdown]';
const graphTypeDropdodwn = '[data-testid=page-control-bar--left] [data-testid=view-type--dropdown]';
const customizeGraphButton = '[data-testid=page-control-bar--left] [data-testid=cog-cell--button]';
const saveAsButton = '//button[./span[text() = \'Save As\']]';
const viewArea = '.time-machine--view';
const viewRawToggle = '[data-testid=raw-data--toggle]';
const autorefreshDropdown = 'div.autorefresh-dropdown';
//const pausedAutorefreshButton = 'button.autorefresh-dropdown--pause'; //Present only when autorefresh is paused - not good candidate for page loade check
const timeRangeDropdown = '//*[@data-testid=\'flex-box\']/div[3]';
//const scriptEditToggle = '[data-testid=switch-to-script-editor] '; //N.B. disappears when in Script edit mode - not good candidate for page load check
//const queryBuildToggle = '[data-testid=switch-to-query-builder]'; //N.B. not present when in Query builder mode - not good candidate for page load check
const submitQueryButton = '[data-testid=time-machine-submit-button]';
//TODO - more controls
const urlCtx = 'data-explorer';
class dataExplorerPage extends influxPage {
constructor(driver){
super(driver);
}
async isLoaded(){
await super.isLoaded([{type: 'css', selector: timeLocaleDropdown},
{type: 'css', selector: graphTypeDropdodwn},
{type: 'css', selector: customizeGraphButton},
{type: 'xpath', selector: saveAsButton},
{type: 'css', selector: viewArea},
{type: 'css', selector: viewRawToggle},
{type: 'css', selector: autorefreshDropdown},
{type: 'xpath', selector: timeRangeDropdown},
{type: 'css', selector: submitQueryButton}
], urlCtx);
}
async getTimeLocaleDropdown(){
return await this.driver.findElement(By.css(timeLocaleDropdown));
}
async getGraphTypeDropdown(){
return await this.driver.findElement(By.css(graphTypeDropdodwn));
}
async getCustomizeGraphButton(){
return await this.driver.findElement(By.css(customizeGraphButton));
}
async getSaveAsButton(){
return await this.driver.findElement(By.xpath(saveAsButton));
}
async getViewArea(){
return await this.driver.findElement(By.css(viewArea));
}
async getViewRawToggle(){
return await this.driver.findElement(By.css(viewRawToggle));
}
async getAutoRefreshDropdown(){
return await this.driver.findElement(By.css(autorefreshDropdown));
}
async getTimeRangeDropdown(){
return await this.driver.findElement(By.xpath(timeRangeDropdown));
}
async getSubmitQueryButton(){
return await this.driver.findElement(By.css(submitQueryButton));
}
//TODO - more element getters
}
module.exports = dataExplorerPage;

View File

@ -0,0 +1,88 @@
const influxPage = require(__srcdir + '/pages/influxPage.js');
const { By } = require('selenium-webdriver');
const logoutButton = '[data-testid=button][title=Logout]';
const getStartedDataCollect = '//*[@data-testid=\'panel\'][./div[contains(@class, \'getting-started\')]][.//span[text()=\'Load your data\']]';
const getStartedDashboard = '//*[@data-testid=\'panel\'][./div[contains(@class, \'getting-started\')]][.//span[text()=\'Build a dashboard\']]';
const getStartedAlerting = '//*[@data-testid=\'panel\'][./div[contains(@class, \'getting-started\')]][.//span[text()=\'Set up alerting\']]';
const dataCollectButton = '[data-testid=button][title=\'Load your data\']';
const dashboardButton = '[data-testid=button][title=\'Build a dashboard\']';
const alertingButton = '[data-testid=button][title=\'Set up alerting\']';
const tutorialsList = '//ul[contains(@class, \'tutorials\')]';
const dashboardsList = '//*[./div/*[text()=\'Recent Dashboards\']]';
const usefulLinkList = '//div[contains(@class,\'cf-col-sm-4 cf-col-md-3\')]//div[@data-testid=\'panel\'][3]//ul';
//const dashboardLink = '//*[@data-testid=\'panel\'][.//*[text()=\'Dashboards\']]//ul/li/a[text()=\'%TEXT%\']';
const dashboardLink = '//*[@data-testid=\'panel\'][.//*[text()=\'Recent Dashboards\']]//*[text()=\'%TEXT%\']'
// TODO - add selectors - especially for isLoaded below
class homePage extends influxPage {
constructor(driver){
super(driver);
}
async isLoaded(){
await super.isLoaded([//{type: 'css', selector: logoutButton},
{type: 'xpath', selector: getStartedDataCollect},
{type: 'xpath', selector: getStartedDashboard},
{type: 'xpath', selector: getStartedAlerting},
{type: 'xpath', selector: tutorialsList},
{type: 'xpath', selector: usefulLinkList},
]);
}
async getLogoutButton(){
return await this.driver.findElement(By.css(logoutButton));
}
async getGetStartedDataCollect(){
return await this.driver.findElement(By.xpath(getStartedDataCollect));
}
async getGetStartedDashboard(){
return await this.driver.findElement(By.xpath(getStartedDashboard));
}
async getGetStartedAlerting(){
return await this.driver.findElement(By.xpath(getStartedAlerting));
}
async getTutorialsList(){
return await this.driver.findElement(By.xpath(tutorialsList));
}
async getUsefulLinksList(){
return await this.driver.findElement(By.xpath(usefulLinkList));
}
async getTutorialLinkByText(text){
return await this.driver.findElement(By.xpath(`${tutorialsList}//a[text() = '${text}']`));
}
async getUsefulLinkByText(text){
return await this.driver.findElement(By.xpath(`${usefulLinkList}//a[contains(text(), '${text}')]`));
}
async getDashboardsList(){
return await this.driver.findElement(By.xpath(dashboardsList));
}
async getDataCollectButton(){
return await this.driver.findElement(By.css(dataCollectButton));
}
async getDashboardButton(){
return await this.driver.findElement(By.css(dashboardButton));
}
async getAlertingButton(){
return await this.driver.findElement(By.css(alertingButton));
}
async getDashboardLink(text){
return await this.driver.findElement(By.xpath(dashboardLink.replace('%TEXT%', text)));
}
}
module.exports = homePage;

161
e2e/src/pages/influxPage.js Normal file
View File

@ -0,0 +1,161 @@
const basePage = require(__srcdir + '/pages/basePage.js');
const { By } = require('selenium-webdriver');
const navMenu = '[data-testid=tree-nav]';
const navMenuHome = '[data-testid=tree-nav--header]';
const navMenuDExplorer = '[data-testid=nav-item-data-explorer]';
const navMenuDashboards = '[data-testid=nav-item-dashboards]';
const navMenuTasks = '[data-testid=nav-item-tasks]';
const navMenuAlerting = '[data-testid=nav-item-alerting]'; //N.B. only available with alerting on
const navMenuLoadData = '[data-testid=nav-item-load-data]';
const navMenuSettings = '[data-testid=nav-item-settings]';
//const navMenuFeedback = '[data-testid=nav-menu--item] span.nav-chat';
const navMenuUser = '[data-testid=user-nav]';
const navMenuOrg = '[data-testid=nav-item-org]';
const navMenuHomeHeading = '//a[text() = \'admin (qa)\']';
const navMenuHomeNewOrg = 'a.cf-nav--sub-item[href=\'/orgs/new\']';
const navMenuHomeLogout = 'a.cf-nav--sub-item[href=\'/logout\']';
const navMenuXpath = '//*[@data-testid = \'nav-menu\']';
const userMenuItemXpath = '//*[@data-testid = \'user-nav\']';
const userMenuItem = '[data-testid^=\'user-nav-item-%ITEM%\']';
const pageHeader = '[data-testid=page-header]';
const urlCtx = 'orgs';
class influxPage extends basePage {
constructor(driver){
super(driver);
}
async getNavMenu(){
return await this.driver.findElement(By.css(navMenu));
}
async getPageHeader(){
return await this.driver.findElement(By.css(pageHeader));
}
/*
async isLoaded(){
await super.isLoaded([{type:'css', selector:navMenu}], urlCtx)
}*/
//N.B. Method overloading not supported in JS - however this page is extended
async isLoaded(selectors = undefined, url = undefined){
if(!selectors){
await super.isLoaded([{type:'css', selector:navMenu},
{type: 'css', selector: navMenuHome},
{type: 'css', selector: navMenuDExplorer},
{type: 'css', selector: navMenuDashboards},
{type: 'css', selector: navMenuTasks},
//{type: 'css', selector: navMenuAlerting}, //N.B. only available with alerting on
{type: 'css', selector: navMenuLoadData},
{type: 'css', selector: navMenuSettings},
{type: 'css', selector: navMenuUser},
// {type: 'css', selector: navMenuOrg}, // TODO - reactivate when certain orgs should be in menu
{type: 'css', selector: pageHeader}
], urlCtx);
return;
}
if(url){
await super.isLoaded(selectors.concat([{type: 'css', selector: navMenu},
{type: 'css', selector: pageHeader}]),url);
}else{
await super.isLoaded(selectors.concat([{type: 'css', selector: navMenu},
{type: 'css', selector: pageHeader}]), urlCtx);
}
}
async getMenuHome(){
return await this.driver.findElement(By.css(navMenuHome));
}
async getMenuExplorer(){
return await this.driver.findElement(By.css(navMenuDExplorer));
}
async getMenuDashboards(){
return await this.driver.findElement(By.css(navMenuDashboards));
}
async getMenuTasks(){
return await this.driver.findElement(By.css(navMenuTasks));
}
/* N.B. only available with alerting enabled
async getMenuAlerting(){
return await this.driver.findElement(By.css(navMenuAlerting));
}
*/
async getMenuLoadData(){
return await this.driver.findElement(By.css(navMenuLoadData));
}
async getMenuSettings(){
return await this.driver.findElement(By.css(navMenuSettings));
}
async getMenuFeedback(){
return await this.driver.findElement(By.css(navMenuFeedback));
}
async getMenuHomeHeading(){
return await this.driver.findElement(By.xpath(navMenuHomeHeading));
}
async getMenuHomeNewOrg(){
return await this.driver.findElement(By.css(navMenuHomeNewOrg));
}
async getMenuHomeLogout(){
return await this.driver.findElement(By.css(navMenuHomeLogout));
}
async getNavMenuAlerting(){
return await this.driver.findElement(By.css(navMenuAlerting));
}
async getSubItemByText(text){
return await this.driver.findElement(By.xpath(navMenuXpath + `//*[text() = '${text}']`));
}
async getSubItemContainingText(text){
return await this.driver.findElement(By.xpath(navMenuXpath + `//*[contains(text(), '${text}')]`));
}
async getNavMenuUser(){
return await this.driver.findElement(By.css(navMenuUser));
}
async getNavMenuOrg(){
return await this.driver.findElement(By.css(navMenuOrg));
}
async getUserMenuSwitchOrg(){
return await this.driver.findElement(By.xpath( `${userMenuItemXpath}[text() = 'Switch Organizations']`));
}
async getUserMenuCreateOrg(){
return await this.driver.findElement(By.xpath(`${userMenuItemXpath}[text() = 'Create Organization']`));
}
async getUserMenuLogout(){
return await this.driver.findElement(By.xpath(`${userMenuItemXpath}[text() = 'Logout']`));
}
async getUserMenuItem(item){
return await this.driver.findElement(By.css(userMenuItem.replace('%ITEM%', item.toLowerCase())));
}
}
module.exports = influxPage;

View File

@ -0,0 +1,319 @@
const loadDataPage = require(__srcdir + '/pages/loadData/loadDataPage.js');
const { By } = require('selenium-webdriver');
const bucketCards = '[data-testid^=\'bucket--card \']';
const createBucketBtn = 'button[data-testid=\'Create Bucket\']';
const filterInput = '[data-testid=search-widget]';
const nameSorter = '[data-testid=\'name-sorter\']';
const policySorter = '[data-testid=\'retention-sorter\']';
const bucketCardByName = '[data-testid=\'bucket--card--name %NAME%\']';
const bucketCardSettingsByName = `[data-testid='bucket-card %NAME%'] [data-testid=bucket-settings]`
// Create Bucket Popup
const popupContainer = '[data-testid=overlay--container]';
const popupTitle = '[data-testid=overlay--header] div';
const popupInputName = '[data-testid=bucket-form-name]';
const popupRetentionNever = '[data-testid=retention-never--button]';
const popupRetentionIntervals = '[data-testid=retention-intervals--button]';
const popupCancelButton = '[data-testid=overlay--body] button[title=Cancel]';
const popupDismissButton = '[data-testid=overlay--header] button[class*=dismiss]';
const popupCreateButton = '[data-testid=overlay--body] button[title*=Create]';
const popupRPIntervalControls = '[data-testid=form--element] [data-testid=grid--row]';
const popupRPDurationSelectorButton = '[data-testid=duration-selector--button]';
const popupRPDaysInput = popupRPIntervalControls + ' [data-testid=grid--column]:nth-of-type(1) input';
const popupRPHoursInput = popupRPIntervalControls + ' [data-testid=grid--column]:nth-of-type(2) input';
const popupRPMinutesInput = popupRPIntervalControls + ' [data-testid=grid--column]:nth-of-type(3) input';
const popupRPSecondsInput = popupRPIntervalControls + ' [data-testid=grid--column]:nth-of-type(4) input';
const popupFormError = '[data-testid=form--element-error]';
//Edit Bucket Popup
const popupSaveChanges = '[data-testid=bucket-form-submit]';
const popupHelpText = '[data-testid=form--help-text]';
//Add data line protocol Popup Wizard
//reuse popup title above
//reuse dismiss above
const wizardStepTitle = '.cf-overlay--title';
const wizardStepSubTitle = '[data-testid=form-container] [class*=sub-title]';
const wizardRadioUploadFile = '[data-testid=\'Upload File\']';
const wizardRadioManual = '[data-testid=\'Enter Manually\']';
const wizardPrecisionDropdown = '[data-testid=\'wizard-step--lp-precision--dropdown\']';
const wizardDragAndDrop = 'div.drag-and-drop';
const wizardContinueButton = '[data-testid=next]';
const wizardTextArea = '[data-testid=\'line-protocol--text-area\']';
const wizardFinishButton = '[data-testid=next][title=\'Finish\']';
const wizardStepStateText = '[data-testid=\'line-protocol--status\']';
const wizardSparkleSpinner = '[data-testid=sparkle-spinner]';
const dataWizardPreviousButton = '[data-testid=overlay--footer] [data-testid=back]';
const popoverItem = '//*[@data-testid=\'popover--contents\']//*[text() = \'%ITEM%\']';
const urlCtx = 'buckets';
class bucketsTab extends loadDataPage {
constructor(driver){
super(driver);
}
async isTabLoaded(){
await super.isTabLoaded(urlCtx);
}
async getBucketCards(){
return await this.driver.findElements(By.css(bucketCards));
}
async getCreateBucketBtn(){
return await this.driver.findElement(By.css(createBucketBtn));
}
async getFilterInput(){
return await this.driver.findElement(By.css(filterInput));
}
async getNameSorter(){
return await this.driver.findElement(By.css(nameSorter));
}
static async getNameSorterSelector(){
return {type: 'css', selector: nameSorter};
}
async getPolicySorter(){
return await this.driver.findElement(By.css(policySorter));
}
//Create Bucket Popup
async getPopupContainer(){
return await this.driver.findElement(By.css(popupContainer));
}
static getPopupContainerSelector(){
return { type: 'css', selector: popupContainer };
}
async getPopupTitle(){
return await this.driver.findElement(By.css(popupTitle));
}
static getPopupTitleSelector(){
return { type: 'css', selector: popupTitle};
}
async getPopupInputName(){
return await this.driver.findElement(By.css(popupInputName));
}
async getPopupRetentionNever(){
return await this.driver.findElement(By.css(popupRetentionNever));
}
async getPopupRetentionIntervals(){
return await this.driver.findElement(By.css(popupRetentionIntervals));
}
async getPopupCancelButton(){
return await this.driver.findElement(By.css(popupCancelButton));
}
async getPopupRPIntevalControls(){
return await this.driver.findElement(By.css(popupRPIntervalControls));
}
static getPopupRPIntervalControlsSelector(){
return { type: 'css', selector: popupRPIntervalControls };
}
async getPopupRPDurationSelectorButton(){
return await this.driver.findElement(By.css(popupRPDurationSelectorButton));
}
static getPopupRPDurationSelectorButtonSelector(){
return {type: 'css', selector: popupRPDurationSelectorButton};
}
async getPopupRPDaysInput(){
return await this.driver.findElement(By.css(popupRPDaysInput));
}
async getPopupRPHoursInput(){
return await this.driver.findElement(By.css(popupRPHoursInput));
}
async getPopupRPMinutesInput(){
return await this.driver.findElement(By.css(popupRPMinutesInput));
}
async getPopupRPSecondsInput(){
return await this.driver.findElement(By.css(popupRPSecondsInput));
}
async getPopupFormError(){
return await this.driver.findElement(By.css(popupFormError));
}
static getPopupFormErrorSelector(){
return { type: 'css', selector: popupFormError };
}
async getPopupDismissButton(){
//return await this.driver.findElement(By.css(popupDismissButton));
return await this.smartGetElement({type: 'css', selector: popupDismissButton});
}
static async getPopupDismissButtonSelector(){
return popupDismissButton;
}
async getPopupRPDurationSelectorItem(duration){
return await this.driver.findElement(By.css(`[data-testid=duration-selector--${duration}]`));
}
async getPopupHelpText(){
return await this.driver.findElement(By.css(popupHelpText));
}
async getPopupCreateButton(){
return await this.driver.findElement(By.css(popupCreateButton));
}
//get just the name link
async getBucketCardName(name){
return await this.driver.findElement(By.css(`[data-testid='bucket--card--name ${name}']`));
}
//get the whole card
async getBucketCardByName(name){
return await this.driver.findElement(By.css(bucketCardByName.replace('%NAME%', name)));
//return await this.driver.findElement(By.xpath(`//div[div/div[@data-testid='bucket--card ${name}']]`));
}
static async getBucketCardDeleteSelectorByName(name){
return {type: 'xpath', selector: `//div[div/div/div[@data-testid='bucket--card ${name}'] ]//*[@data-testid='context-delete-menu']`};
}
async getBucketCardDeleteByName(name){
// return await this.driver.findElement(By.xpath(`//div[div/div/div[@data-testid='bucket--card ${name}'] ]//*[@data-testid='context-delete-menu']`));
return await this.driver.findElement(By.css(`[data-testid='context-delete-menu ${name}']`));
}
async getBucketCardRetentionByName(name){
// return await this.driver.findElement(By.xpath(`//div[div/div[@data-testid='bucket--card ${name}']]//div[contains(text(), 'Retention')]`));
//return await this.driver.findElement(By.xpath(`//*[@data-testid='bucket--card ${name}']//*[@data-testid='cf-resource-card--meta-item'][contains(text(),"Retention")]`));
//return await this.driver.findElement(By.xpath(`//*[@data-testid='bucket-card'][.//*[@data-testid='bucket--card--name ${name}']]//*[@data-testid='cf-resource-card--meta-item'][contains(text(), 'Retention')]`));
return await this.driver.findElement(By.xpath(`//*[@data-testid='bucket-card ${name}']//*[@data-testid='resource-list--meta']//*[contains(text(), 'Retention')]`));
}
async getBucketCardPopover(){
//return await this.driver.findElement(By.xpath(`//div[div/div[@data-testid='bucket--card ${name}']]//div[@data-testid='popover--contents']`));
return await this.driver.findElement(By.css('[data-testid=popover--contents]'));
}
static async getPopoverSelector(){
return { type: 'css', selector: '[data-testid=popover--contents]'};
}
async getBucketCardPopoverItemByName(name, item){
return await this.driver.findElement(By.xpath(`//div[div/div[@data-testid='bucket--card ${name}']]//div[@data-testid='popover--contents']//div[contains(text(), '${item}')]`));
}
static async getBucketCardSelectorByName(name){
return {type: 'css', selector: `[data-testid='bucket--card ${name}']`};
}
async getBucketCardDeleteConfirmByName(name){
//return await this.smartGetElement({type: 'xpath', selector: `//div[div/div/div[@data-testid='bucket--card ${name}'] ]//*[@data-testid='context-delete-menu']/..//button[text() = 'Confirm']`});
//return await this.driver.findElement(By.xpath(`//div[div/div/div[@data-testid='bucket--card ${name}'] ]//*[@data-testid='context-delete-menu']/..//button[text() = 'Confirm']`));
return await this.driver.findElement(By.css(`[data-testid='context-delete-bucket ${name}'] `));
}
async getBucketCardAddDataByName(name){
//return await this.driver.findElement(By.xpath(`//*[@data-testid='bucket-card'][.//*[@data-testid='bucket--card--name ${name}']]//button[@title='Add Data']`));
return await this.driver.findElement(By.xpath(`//*[@data-testid='bucket-card ${name}']//button[@title='Add Data']`))
//return await this.smartGetElement({type: 'xpath', selector: `//div[div/div/div[@data-testid='bucket--card ${name}']]//button[@title = 'Add Data']`});
}
async getPopupSaveChanges(){
return await this.driver.findElement(By.css(popupSaveChanges));
}
async getWizardStepTitle(){
return await this.driver.findElement(By.css(wizardStepTitle));
}
static async getWizardStepTitleSelector(){
return {type: 'css', selector: wizardStepTitle };
}
async getWizardStepSubTitle(){
return await this.driver.findElement(By.css(wizardStepSubTitle));
}
static async getWizardStepSubTitleSelector(){
return {type: 'css', selector: wizardStepSubTitle };
}
async getWizardRadioUploadFile(){
return await this.driver.findElement(By.css(wizardRadioUploadFile));
}
async getWizardRadioManual(){
return await this.driver.findElement(By.css(wizardRadioManual));
}
async getWizardPrecisionDropdown(){
return await this.driver.findElement(By.css(wizardPrecisionDropdown));
}
async getWizardDragAndDrop(){
return await this.driver.findElement(By.css(wizardDragAndDrop));
}
async getWizardContinueButton(){
return await this.driver.findElement(By.css(wizardContinueButton));
}
static async getWizardContinueButtonSelector(){
return {type: 'css', selector: wizardContinueButton };
}
async getWizardTextArea(){
return await this.driver.findElement(By.css(wizardTextArea));
}
async getWizardDropdownPrecisionItem(prec){
return await this.driver.findElement(By.css(`[data-testid='wizard-step--lp-precision-${prec}']`));
}
async getWizardFinishButton(){
return await this.driver.findElement(By.css(wizardFinishButton));
}
async getWizardStepStateText(){
return await this.driver.findElement(By.css(wizardStepStateText));
}
async getWizardSparkleSpinner(){
return await this.driver.findElement(By.css(wizardSparkleSpinner));
}
async getPopoverItem(item){
return await this.driver.findElement(By.xpath(popoverItem.replace('%ITEM%', item)));
}
async getDataWizardPreviousButton(){
return await this.driver.findElement(By.css(dataWizardPreviousButton));
}
async getBucketCardSettingsByName(name){
return await this.driver.findElement(By.css(bucketCardSettingsByName.replace('%NAME%', name)));
}
}
module.exports = bucketsTab;

View File

@ -0,0 +1,43 @@
const { By } = require('selenium-webdriver');
const loadDataPage = require(__srcdir + '/pages/loadData/loadDataPage.js');
const libTileByName = '[data-testid=\'client-libraries-cards--%NAME%\']';
const clientURL = '[data-testid=tabs--tab-contents] .code-snippet code';
const copy2ClipByLabel = '//*[text() = \'%LABEL%\']/following-sibling::div[1]//button';
const urlCtx = 'client-libraries';
class clientLibsTab extends loadDataPage {
constructor(driver) {
super(driver);
}
async isTabLoaded(){
await super.isTabLoaded(urlCtx,
[
{type: 'css', selector: clientURL},
{type: 'css', selector: libTileByName.replace('%NAME%', 'csharp')},
{type: 'css', selector: libTileByName.replace('%NAME%', 'java')},
{type: 'css', selector: libTileByName.replace('%NAME%', 'python')},
]
);
}
async getLibTileByName(name){
return await this.driver.findElement(By.css(libTileByName.replace('%NAME%', name)));
}
async getClientURL(){
return await this.driver.findElement(By.css(clientURL));
}
async getCopy2ClipByLabel(label){
return await this.driver.findElement(By.xpath(copy2ClipByLabel.replace('%LABEL%', label)));
}
}
module.exports = clientLibsTab;

View File

@ -0,0 +1,146 @@
const influxPage = require(__srcdir + '/pages/influxPage.js');
const { By } = require('selenium-webdriver');
const tabsCss = '[data-testid=tabs]';
const tabsXpath = '//*[@data-testid=\'tabs\']';
const pageTitle = '[data-testid=\'page-title\']';
const urlCtx = 'settings';
//Create Scraper Popup - accessible in both Scraper Page and base LoadData page - through buckets add data
const createScraperTitle = '[data-testid=overlay--header] [class*=title]';
const createScraperDismiss = '[data-testid=overlay--header] button';
const createScraperNameInput = '[data-testid=input-field][title=Name]';
const createScraperBucketDropdown = '[data-testid=bucket-dropdown--button]';
const createScraperUrlInput = '[data-testid=input-field][title*=URL]';
const createScraperCancel = '[data-testid=create-scraper--cancel]';
const createScraperSubmit = '[data-testid=create-scraper--submit]';
const createScraperBucketDropdownItems = '[data-testid=bucket-dropdown] [data-testid=dropdown-item]';
//Create Telegraf Config Wizard - accessible from buckets and telegraf
const bucketDropdown = '[data-testid=bucket-dropdown]';
const pluginsFilter = '[data-testid=input-field][placeholder*=Plugins]';
const telegrafNameInput = '[data-testid=input-field][title$=\'Name\']';
const telegrafDescrInput = '[data-testid=input-field][title$=\'Description\']';
//common controls in basePage
const codeSnippetWithToken = 'div.code-snippet:first-of-type';
const codeSnippetWithTelegrafCommand = 'div.code-snippet:nth-of-type(2)';
class loadDataPage extends influxPage {
constructor(driver){
super(driver);
}
async isLoaded(){
await super.isLoaded([{type: 'css', selector: tabsCss}], urlCtx);
}
async isTabLoaded(tabUrlPart, selectors = undefined){
if(selectors) {
await super.isLoaded(selectors.concat([{type: 'css', selector: tabsCss}]), tabUrlPart);
}else{
await super.isLoaded([{type: 'css', selector: tabsCss}], tabUrlPart);
}
}
async getTabByName(name){
return await this.driver.findElement(By.xpath(`${tabsXpath}//div[@data-testid='tabs--tab' and @id='${name.toLowerCase().replace(' ', '-')}']`));
}
async getPageTitle(){
return await this.driver.findElement(By.css(pageTitle));
}
//Create Scraper Popup
async getCreateScraperTitle(){
return await this.driver.findElement(By.css(createScraperTitle));
}
static getCreateScraperTitleSelector(){
return { type: 'css', selector: createScraperTitle};
}
async getCreateScraperDismiss(){
return await this.driver.findElement(By.css(createScraperDismiss));
}
async getCreateScraperNameInput(){
return await this.driver.findElement(By.css(createScraperNameInput));
}
static getCreateScraperNameInputSelector(){
return { type: 'css', selector: createScraperNameInput};
}
async getCreateScraperBucketDropdown(){
return await this.driver.findElement(By.css(createScraperBucketDropdown));
}
async getCreateScraperUrlInput(){
return await this.driver.findElement(By.css(createScraperUrlInput));
}
static getCreateScraperUrlInputSelector(){
return { type: 'css', selector: createScraperUrlInput};
}
async getCreateScraperCancel(){
return await this.driver.findElement(By.css(createScraperCancel));
}
async getCreateScraperSubmit(){
return await this.driver.findElement(By.css(createScraperSubmit));
}
async getCreateScraperBucketDropdownItem(item){
return await this.driver.findElement(By.xpath(`//*[@data-testid='dropdown-item']/div[text() = '${item}']`));
}
static getCreateScraperBucketDropdownItemSelector(item){
return `//*[@data-testid='dropdown-item']/div[text() = '${item}']`;
}
static getCreateScraperBucketDropdownItemsSelector(){
return {type: 'css', selector: createScraperBucketDropdownItems};
}
//Create telegraf wizard
async getBucketDropdown(){
return await this.driver.findElement(By.css(bucketDropdown));
}
async getPluginsFilter(){
return await this.driver.findElement(By.css(pluginsFilter));
}
async getPluginTileByName(name){
return await this.driver.findElement(By.css(`[data-testid=telegraf-plugins--${name}]`));
}
async getBucketDropdownItem(item){
return await this.driver.findElement(
By.xpath(`//*[@data-testid='dropdown-item'][div[text()='${item}']]`));
}
async getTelegrafNameInput(){
return await this.driver.findElement(By.css(telegrafNameInput));
}
async getTelegrafDescrInput(){
return await this.driver.findElement(By.css(telegrafDescrInput));
}
async getCodeSnippetWithToken(){
return await this.driver.findElement(By.css(codeSnippetWithToken));
}
async getCodeSnippetWithTelegrafCommand(){
return await this.driver.findElement(By.css(codeSnippetWithTelegrafCommand));
}
}
module.exports = loadDataPage;

View File

@ -0,0 +1,93 @@
const { By } = require('selenium-webdriver');
const loadDataPage = require(__srcdir + '/pages/loadData/loadDataPage.js');
const basePage = require(__srcdir + '/pages/basePage.js');
const scrapersFilter = '[data-testid=search-widget]';
const createScraperHeader = '[data-testid=create-scraper-button-header]';
const nameSort = '[data-testid=resource-list--sorter]:nth-of-type(1)';
const urlSort = '[data-testid=resource-list--sorter]:nth-of-type(2)';
const bucketSort = '[data-testid=resource-list--sorter]:nth-of-type(3)';
const createScraperEmpty = '[data-testid=create-scraper-button-empty]';
const scraperCards = '[data-testid=resource-card]';
const urlCtx = 'scrapers';
class scrapersTab extends loadDataPage{
constructor(driver){
super(driver);
}
async isTabLoaded(){
await super.isTabLoaded(urlCtx,
[
{type: 'css', selector: scrapersFilter},
{type: 'css', selector: createScraperHeader},
basePage.getSortTypeButtonSelector()
//{type: 'css', selector: nameSort},
//{type: 'css', selector: urlSort},
//{type: 'css', selector: bucketSort},
]
);
}
async getScraperCardByName(name){
return await this.driver.findElement(By.xpath(`//*[@data-testid='resource-card'][//span[text() = '${name}']]`));
}
async getScraperCardName(name){
return await this.driver.findElement(By.xpath(`//*[@data-testid='resource-editable-name']//span[text()='${name}']`));
}
async getScraperCardNameEditButton(name){
return await this.driver.findElement(By.xpath(`//*[./*[@data-testid='resource-editable-name'][.//span[text()='${name}']]]//*[@data-testid='editable-name']`));
}
async getScraperCardNameEditField(name){
return await this.driver.findElement(By.xpath(`//*[@data-testid='input-field'][@value='${name}']`));
}
async getScraperCardDeleteByName(name){
return await this.driver.findElement(By.xpath(`//*[@data-testid='resource-card'][.//span[text()='${name}']]//*[@data-testid='context-menu']`));
}
async getScraperCardDeleteConfirmByName(name){
return await this.driver.findElement(By.xpath(`//*[@data-testid='resource-card'][.//span[text()='${name}']]//*[@data-testid='confirmation-button']`));
}
async getScrapersFilter(){
return this.driver.findElement(By.css(scrapersFilter));
}
async getCreateScraperHeader(){
return this.driver.findElement(By.css(createScraperHeader));
}
async getNameSort(){
return this.driver.findElement(By.css(nameSort));
}
async getUrlSort(){
return this.driver.findElement(By.css(urlSort));
}
async getBucketSort(){
return this.driver.findElement(By.css(bucketSort));
}
async getCreateScraperEmpty(){
return this.driver.findElement(By.css(createScraperEmpty));
}
static getCreateScraperEmptySelector(){
return { type: 'css', selector: createScraperEmpty};
}
async getScraperCards(){
return await this.driver.findElements(By.css(scraperCards));
}
}
module.exports = scrapersTab;

View File

@ -0,0 +1,317 @@
const { By } = require('selenium-webdriver');
const loadDataPage = require(__srcdir + '/pages/loadData/loadDataPage.js');
const basePage = require(__srcdir + '/pages/basePage.js');
const telegrafsFilter = '[data-testid=search-widget]';
const createConfigInHeader = '//div[@data-testid=\'tabbed-page--header\']//*[contains(@title, \'Create\')]';
const nameSort = '[data-testid=name-sorter]';
const bucketSort = '[data-testid=bucket-sorter]';
const createConfigInBody = '[data-testid=resource-list] [data-testid=button]';
const telegrafCardTemplate = '//*[@data-testid=\'resource-card\'][.//*[text()=\'%NAME%\']]';
const telegrafCards = '[data-testid=resource-card]';
const telegrafCardSetupInstructions = '//*[@data-testid=\'resource-card\'][.//*[text()=\'%NAME%\']]//*[@data-testid=\'setup-instructions-link\']';
const telegrafCardName = '//*[@data-testid=\'collector-card--name\'][span[text()=\'%NAME%\']]';
const telegrafCardNameEditBtn = '//*[./*[@data-testid=\'collector-card--name\'][span[text()=\'%NAME%\']]]//*[@data-testid=\'collector-card--name-button\']';
const telegrafCardNameInput = '[data-testid=\'collector-card--input\']'; //should only be one active
const telegrafCardDescr = '//*[@data-testid=\'resource-card\'][.//*[text()=\'%NAME%\']]//*[@data-testid=\'resource-list--editable-description\']';
const telegrafCardDescrEditBtn = '//*[@data-testid=\'resource-card\'][.//*[text()=\'%NAME%\']]//*[@data-testid=\'resource-list--editable-description\']//*[@data-testid=\'icon\']';
const telegrafCardDescrInput = '//*[@data-testid=\'resource-card\'][.//*[text()=\'%NAME%\']]//div[@data-testid=\'resource-list--editable-description\']//input';
const telegrafCardDelete = '//*[@data-testid=\'resource-card\'][.//*[text()=\'%NAME%\']]//*[@data-testid=\'context-menu\']';
const telegrafCardDeleteConfirm = '//*[@data-testid=\'resource-card\'][.//*[text()=\'%NAME%\']]//button[@data-testid=\'context-menu-item\']';
const telegrafCardAddLabelBtn = '//*[@data-testid=\'resource-card\'][.//*[text()=\'%NAME%\']]//*[@data-testid=\'inline-labels--add\']';
const telegrafCardLabelPopup = '[data-testid=inline-labels--popover--dialog]';
const telegrafCardLabelPopupListItem = '[data-testid=\'inline-labels--popover--dialog\'] [data-testid=\'label-list--item %ITEM%\']';
const telegrafCardLabelPillItem = '//*[@data-testid=\'resource-card\'][.//*[text()=\'%NAME%\']]//*[@data-testid=\'label--pill %ITEM%\']';
const telegrafCardLabelPopupFilter = '[data-testid=\'inline-labels--popover--dialog\'] [data-testid=\'inline-labels--popover-field\']';
const telegrafCardLabelEmptyState = '[data-testid=\'inline-labels--popover--dialog\'] [data-testid=\'empty-state--text\']';
const telegrafCardLabelPillDelete = '//*[@data-testid=\'resource-card\'][.//*[text()=\'%NAME%\']]//*[@data-testid=\'label--pill--delete %ITEM%\']';
const urlCtx = 'telegrafs';
// Telegraf wizard
const bucketDropdownBtn = '[data-testid=bucket-dropdown--button] ';
const pluginFilter = '[data-testid=input-field][placeholder*=\'Plugins\']';
const pluginTileTemplate = '[data-testid=telegraf-plugins--%TILE_NAME%]';
// Telegraf wizard step 2
const configurationNameInput = '[data-testid=input-field][title*=\'Configuration Name\']';
const configurationDescrInput = '[data-testid=input-field][title*=\'Configuration Descr\']';
const configurationPluginsSideBar = '//*[*[text()=\'Plugins\']]//div[contains(@class,\'side-bar--tabs\')]';
//Telegraf wizard edit plugin
const pluginDockerEditEndpoint = '//*[label/span[text()=\'endpoint\']]//*[@data-testid=\'input-field\']';
const pluginK8SEditEndpoint = '//*[label/span[text()=\'url\']]//*[@data-testid=\'input-field\']';
const pluginNGINXEditEndpoint = '//*[label/span[text()=\'urls\']]//*[@data-testid=\'input-field\']';
const pluginNGINXAddUrlButton = '[data-testid=button][title=\'Add to list of urls\']';
const pluginNGINXDeleteFirstURL = '[data-testid=confirmation-button--button][title=\'Delete\']:nth-of-type(1)';
const pluginNGINXDeleteURLConfirmButton = '[data-testid=confirmation-button--confirm-button]';
const pluginNGINXURLListItems = '[data-testid=overlay--body] [data-testid=\'grid--column\'] [data-testid=index-list]';
const pluginRedisServersEditEndpoint = '//*[label/span[text()=\'servers\']]//*[@data-testid=\'input-field\']';
const pluginRedisPasswordEditEndpoint = '//*[label/span[text()=\'password\']]//*[@data-testid=\'input-field\']';
//Telegraf wizard step 3
const codeToken = '//pre[contains(text(), \'TOKEN\')]';
const codeCliTelegraf = '//code[contains(text(), \'telegraf\')]';
const copyToClipboardToken = '//*[@class=\'code-snippet\'][.//*[contains(text(),\'TOKEN\')]]//*[@data-testid=\'button-copy\']';
const copyToClipboardCommand = '//*[@class=\'code-snippet\'][.//*[contains(text(),\'telegraf\')]]//*[@data-testid=\'button-copy\']';
//Config Popup
const downloadConfigButton = '//*[@data-testid=\'button\'][span[text()=\'Download Config\']]';
class telegrafsTab extends loadDataPage{
constructor(driver){
super(driver);
}
async isTabLoaded(){
await super.isTabLoaded(urlCtx,
[
{type: 'css', selector: telegrafsFilter},
{type: 'xpath', selector: createConfigInHeader},
//{type: 'css', selector: nameSort},
basePage.getSortTypeButtonSelector()
//{type: 'css', selector: bucketSort},
]
);
}
async getTelegraphCardByName(name){
return await this.driver.findElement(By.xpath(`//*[@data-testid='resource-card'][//span[text()='${name}']]`));
}
async getTelegrafsFilter(){
return await this.driver.findElement(By.css(telegrafsFilter));
}
async getCreateConfigInHeader(){
return await this.driver.findElement(By.xpath(createConfigInHeader));
}
async getNameSort(){
return await this.driver.findElement(By.css(nameSort));
}
async getBucketSort(){
return await this.driver.findElement(By.css(bucketSort));
}
async getCreateConfigInBody(){
return await this.driver.findElement(By.css(createConfigInBody));
}
// Telegraf Wizard
async getBucketDropdownBtn(){
return await this.driver.findElement(By.css(bucketDropdownBtn));
}
async getPluginFilter(){
return await this.driver.findElement(By.css(pluginFilter));
}
async getPluginTileByName(name){
return await this.driver.findElement(By.css(pluginTileTemplate.replace('%TILE_NAME%', name)));
}
static getPluginTitleSelectorByName(name){
return { type: 'css', selector: pluginTileTemplate.replace('%TILE_NAME%', name)};
}
// Telegraf Wizard step 2
async getPluginItemByName(name){
return await this.driver.findElement(By.xpath(`//*[contains(@class, 'side-bar--tab')]/div[.//*[text() = '${name.toLowerCase()}']]`));
}
async getConfigurationNameInput(){
return await this.driver.findElement(By.css(configurationNameInput));
}
static getConfigurationNameInputSelector(){
return { type: 'css', selector: configurationNameInput};
}
async getConfigurationDescrInput(){
return await this.driver.findElement(By.css(configurationDescrInput));
}
static getConfigurationDescrInputSelector(){
return { type: 'css', selector: configurationDescrInput};
}
async getConfigurationPluginsSideBar(){
return await this.driver.findElement(By.xpath(configurationPluginsSideBar));
}
static configurationPluginsSideBarSelector(){
return { type: 'css', selector: configurationPluginsSideBar};
}
//Telegraf wizard edit plugin
async getPluginDockerEditEndpoint(){
return await this.driver.findElement(By.xpath(pluginDockerEditEndpoint));
}
async getPluginK8SEditEndpoint(){
return await this.driver.findElement(By.xpath(pluginK8SEditEndpoint));
}
async getPluginNGINXEditEndpoint(){
return await this.driver.findElement(By.xpath(pluginNGINXEditEndpoint));
}
async getPluginNGINXAddUrlButton(){
return await this.driver.findElement(By.css(pluginNGINXAddUrlButton));
}
async getPluginNGINXDeleteFirstURL(){
return await this.driver.findElement(By.css(pluginNGINXDeleteFirstURL));
}
async getPluginNGINXDeleteURLConfirmButton(){
return await this.driver.findElement(By.css(pluginNGINXDeleteURLConfirmButton));
}
async getPluginNGINXURLListItems(){
return await this.driver.findElements(By.css(pluginNGINXURLListItems));
}
static getPluginNGINXURLListItemsSelector(){
return { type: 'css', selector: pluginNGINXURLListItems };
}
async getPluginRedisServersEditEndpoint(){
return await this.driver.findElement(By.xpath(pluginRedisServersEditEndpoint));
}
async getPluginRedisPasswordEditEndpoint(){
return await this.driver.findElement(By.xpath(pluginRedisPasswordEditEndpoint));
}
async getCodeToken(){
return await this.driver.findElement(By.xpath(codeToken));
}
async getCodeCliTelegraf(){
return await this.driver.findElement(By.xpath(codeCliTelegraf));
}
// Config popup
async getDownloadConfigButton(){
return await this.driver.findElement(By.xpath(downloadConfigButton));
}
// Telegraf Card List
async getTelegrafCardByName(name){
return await this.driver.findElement(By.xpath(telegrafCardTemplate.replace('%NAME%', name)));
}
static getTelegrafCardSelectorByName(name){
return { type: 'xpath', selector: telegrafCardTemplate.replace('%NAME%', name) };
}
async getTelegrafCards(){
return await this.driver.findElements(By.css(telegrafCards));
}
async getCopyToClipboardToken(){
return await this.driver.findElement(By.xpath(copyToClipboardToken));
}
async getCopyToClipboardCommand(){
return await this.driver.findElement(By.xpath(copyToClipboardCommand));
}
async getTelegrafCardSetupInstructions(card){
return await this.driver.findElement(By.xpath(telegrafCardSetupInstructions.replace('%NAME%', card)));
}
async getTelegrafCardName(name){
return await this.driver.findElement(By.xpath(telegrafCardName.replace('%NAME%', name)));
}
async getTelegrafCardNameEditBtn(name){
return await this.driver.findElement(By.xpath(telegrafCardNameEditBtn.replace('%NAME%', name)));
}
async getTelegrafCardNameInput(name){
return await this.driver.findElement(By.css(telegrafCardNameInput.replace('%NAME%', name)));
}
async getTelegrafCardDescr(name){
return await this.driver.findElement(By.xpath(telegrafCardDescr.replace('%NAME%', name)));
}
async getTelegrafCardDescrEditBtn(name){
return await this.driver.findElement(By.xpath(telegrafCardDescrEditBtn.replace('%NAME%', name)));
}
async getTelegrafCardDescrInput(name){
return await this.driver.findElement(By.xpath(telegrafCardDescrInput.replace('%NAME%', name)));
}
async getTelegrafCardDelete(name){
return await this.driver.findElement(By.xpath(telegrafCardDelete.replace('%NAME%', name)));
}
async getTelegrafCardDeleteConfirm(name){
return await this.driver.findElement(By.xpath(telegrafCardDeleteConfirm.replace('%NAME%', name)));
}
async getTelegrafCardAddLabelBtn(name){
return await this.driver.findElement(By.xpath(telegrafCardAddLabelBtn.replace('%NAME%', name)));
}
async getTelegrafCardLabelPopup(name){
return await this.driver.findElement(By.css(telegrafCardLabelPopup.replace('%NAME%', name)));
}
static getTelegrafCardLabelPopupSelector(name){
return { type: 'css', selector: telegrafCardLabelPopup.replace('%NAME%', name)};
}
async getTelegrafCardLabelPopupListItem(name, item){
return await this.driver.findElement(By.css(telegrafCardLabelPopupListItem
.replace('%ITEM%', item)));
}
static getTelegrafCardLabelPopupListItemSelector(name, item){
return { type: 'css', selector: telegrafCardLabelPopupListItem
.replace('%ITEM%', item)};
}
async getTelegrafCardLabelPillItem(name, item){
return await this.driver.findElement(By.xpath(telegrafCardLabelPillItem
.replace('%NAME%', name)
.replace('%ITEM%', item)));
}
static getTelegrafCardLabelPillItemSelector(name, item){
return { type: 'xpath', selector: telegrafCardLabelPillItem
.replace('%NAME%', name)
.replace('%ITEM%', item)};
}
async getTelegrafCardLabelPopupFilter(name){
return await this.driver.findElement(By.css(telegrafCardLabelPopupFilter.replace('%NAME%', name)));
}
async getTelegrafCardLabelEmptyState(name){
return await this.driver.findElement(By.css(telegrafCardLabelEmptyState.replace('%NAME%', name)));
}
async getTelegrafCardLabelPillDelete(name, item){
return await this.driver.findElement(By.xpath(telegrafCardLabelPillDelete
.replace('%NAME%', name)
.replace('%ITEM%', item)));
}
}
module.exports = telegrafsTab;

View File

@ -0,0 +1,186 @@
const { By } = require('selenium-webdriver');
const settingsPage = require(__srcdir + '/pages/settings/settingsPage.js');
const tokensFilter = '[data-testid=input-field--filter]';
const genTokenButton = '[data-testid=dropdown-button--gen-token]';
const tokenListing = '[data-testid=index-list]';
const descHeader = '[data-testid=index-list--header-cell]:nth-of-type(1)';
const statusHeader = '[data-testid=index-list--header-cell]:nth-of-type(2)';
//const createVariableBody = '[data-testid=button-create-initial]';
const tokenCellTemplate = '//*[@data-testid=\'table-cell\'][.//span[text()="%DESCR%"]]';
const generateTokenDropdownBtn = '[data-testid=dropdown-button--gen-token]';
const generateTokenItem = '[data-testid=\'dropdown-item generate-token--%ITEM%\']';
const tokenCardDisableToggle = '//*[td//span[text() = \'%DESCR%\']]//*[@data-testid=\'slide-toggle\']';
const tokensSortByDescription = '//*[@data-testid=\'index-list--header-cell\'][text()=\'Description\']';
const tokenDescription = '//*[@data-testid=\'editable-name\'][.//span[text()=\'%DESCR%\']]';
const tokenDescriptionEditBtn = '//*[@data-testid=\'editable-name\'][.//span[text()=\'%DESCR%\']]/div[@data-testid=\'editable-name--toggle\']';
const tokenDescriptionEditInput = '//*[@data-testid=\'editable-name\'][.//span[text()=\'%DESCR%\']]//input';
const tokenCardDeleteButton = '//*[@data-testid=\'table-row\'][.//span[text()="%DESCR%"]]//*[@data-testid=\'delete-token--button\']';
// next selector is deprecated - todo clean up
const tokenCardDeleteConfirm = '//*[@data-testid=\'table-row\'][.//span[text()="%DESCR%"]]//*[text()=\'Confirm\']';
const tokenCardPopoverDeletConfirm = '//*[@data-testid=\'delete-token--popover--dialog\']//*[text() = \'Confirm\']';
// Generate Read/Write token popup
const descrInput = '[data-testid=\'input-field--descr\']';
const typeRadioButton = '//*[@data-testid=\'flex-box\'][div[text()=\'%MODE%\']]//*[@data-testid=\'select-group--option\'][@title=\'%SET%\']';
const searchBuckets = '//*[@data-testid=\'flex-box\'][div[text()=\'%MODE%\']]//input[@data-testid=\'input-field\'][contains(@placeholder,\'Search buckets\')]';
const emptyStateText = '//*[@data-testid=\'flex-box\'][div[text()=\'%MODE%\']]//*[@data-testid=\'empty-state--text\']';
const searchBucketsListItem = '//*[@data-testid=\'flex-box\'][div[text()=\'%MODE%\']]//*[@data-testid=\'selector-list %NAME%\']';
const selectAllBuckets = '//*[@data-testid=\'flex-box\'][div[text()=\'%MODE%\']]//*[@title=\'Select All\']';
const deselectAllBuckets = '//*[@data-testid=\'flex-box\'][div[text()=\'%MODE%\']]//*[@title=\'Deselect All\']';
//Generate All Access token popup
const allAccessDescrInput = '[data-testid=form-container] [data-testid=input-field]';
const allAccessCancelButton = '[data-testid=button][title=Cancel]';
const allAccessSaveButton = '[data-testid=button][title=Save]';
//Review token popup
const tokenReviewTokenCode = 'div.code-snippet--text pre code';
const tokenReviewPermissions = '[data-testid=permissions-section]';
const tokenReviewPermissionItem = '//*[@data-testid=\'permissions-section\'][.//h3[text()=\'%ITEM%\']]';
const urlCtx = 'tokens';
class tokensTab extends settingsPage{
constructor(driver){
super(driver);
}
async isTabLoaded(){
await super.isTabLoaded(urlCtx,
[
{type: 'css', selector: tokensFilter},
{type: 'css', selector: genTokenButton},
{type: 'css', selector: tokenListing},
{type: 'css', selector: descHeader},
{type: 'css', selector: statusHeader},
]
);
}
async getTokenCellByDescr(descr){
return await this.driver.findElement(By.xpath(tokenCellTemplate.replace('%DESCR%', descr)));
}
static getTokenCellSelectorByDescr(descr){
return { type: 'xpath', selector: tokenCellTemplate.replace('%DESCR%', descr)};
}
async getGenerateTokenDropdownBtn(){
return await this.driver.findElement(By.css(generateTokenDropdownBtn));
}
async getGenerateTokenItem(item){
return await this.driver.findElement(By.css(generateTokenItem.replace('%ITEM%', item)));
}
async getDescrInput(){
return await this.driver.findElement(By.css(descrInput));
}
async getTypeRadioButton(mode, set){
return await this.driver.findElement(By.xpath(typeRadioButton
.replace('%MODE%',mode)
.replace('%SET%',set)));
}
async getSearchBuckets(mode){
return await this.driver.findElement(By.xpath(searchBuckets.replace('%MODE%', mode)));
}
static getSearchBucketsSelector(mode){
return { type: 'xpath', selector: searchBuckets.replace('%MODE%', mode) };
}
async getEmptyStateText(mode){
return await this.driver.findElement(By.xpath(emptyStateText.replace('%MODE%', mode)));
}
static getEmptyStateTextSelector(mode){
return { type: 'xpath', selector: emptyStateText.replace('%MODE%', mode) };
}
async getSearchBucketsListItem(mode, name){
return await this.driver.findElement(By.xpath(searchBucketsListItem
.replace('%MODE%', mode)
.replace('%NAME%', name)));
}
static getSearchBucketsListItemSelector(mode, name){
return { type: 'xpath', selector: searchBucketsListItem
.replace('%MODE%', mode)
.replace('%NAME%', name)};
}
async getSelectAllBuckets(mode){
return await this.driver.findElement(By.xpath(selectAllBuckets.replace('%MODE%', mode)));
}
async getDeselectAllBuckets(mode){
return await this.driver.findElement(By.xpath(deselectAllBuckets.replace('%MODE%', mode)));
}
async getAllAccessDescrInput(){
return await this.driver.findElement(By.css(allAccessDescrInput));
}
async getAllAccessCancelButton(){
return await this.driver.findElement(By.css(allAccessCancelButton));
}
async getAllAccessSaveButton(){
return await this.driver.findElement(By.css(allAccessSaveButton));
}
async getTokenCardDisableToggle(descr){
return await this.driver.findElement(By.xpath(tokenCardDisableToggle.replace('%DESCR%', descr)));
}
async getTokenCardDescriptions(){
return await this.driver.findElements(By.xpath('//*[@data-testid=\'editable-name\']//span'));
}
async getTokensSortByDescription(){
return await this.driver.findElement(By.xpath(tokensSortByDescription));
}
async getTokenDescription(descr){
return await this.driver.findElement(By.xpath(tokenDescription.replace('%DESCR%', descr)));
}
async getTokenDescriptionEditBtn(descr){
return await this.driver.findElement(By.xpath(tokenDescriptionEditBtn.replace('%DESCR%', descr)));
}
async getTokenDescriptionEditInput(descr){
return await this.driver.findElement(By.xpath(tokenDescriptionEditInput.replace('%DESCR%', descr)));
}
async getTokenReviewTokenCode(){
return await this.driver.findElement(By.css(tokenReviewTokenCode));
}
async getTokenReviewPermissions(){
return await this.driver.findElement(By.css(tokenReviewPermissions));
}
async getTokenReviewPermissionItem(item){
return await this.driver.findElement(By.xpath(tokenReviewPermissionItem.replace('%ITEM%', item)));
}
async getTokenCardDeleteButton(descr){
return await this.driver.findElement(By.xpath(tokenCardDeleteButton.replace('%DESCR%', descr)));
}
async getTokenCardDeleteConfirm(descr){
return await this.driver.findElement(By.xpath(tokenCardDeleteConfirm.replace('%DESCR%', descr)));
}
async getTokenCardPopoverDeletConfirm(){
return await this.driver.findElement(By.xpath(tokenCardPopoverDeletConfirm));
}
}
module.exports = tokensTab;

View File

@ -0,0 +1,266 @@
const influxPage = require(__srcdir + '/pages/influxPage.js');
const { By } = require('selenium-webdriver');
const pageTitle = '[data-testid=\'page-title\']';
const createCheckButton = '[data-testid=create-check]';
const createEndpointButton = '[data-testid=create-endpoint]';
const createRuleButton = '[data-testid=create-rule]';
const checksFilterInput = '[data-testid=\'filter--input checks\']';
const checksQuestionMark = '[data-testid=\'Checks--question-mark\']';
const checksTooltipContents = '[data-testid=\'Checks--question-mark-tooltip--contents\']';
const alertingTab = '[data-testid=alerting-tab--%TABNAME%]';
const createCheckDropdown = '[data-testid=\'checks--column\'] [data-testid=\'dropdown-menu--contents\']';
const createCheckDropdownItem = '[data-testid=\'dropdown-menu--contents\'] [data-testid=create-%ITEM%-check]';
const endpointsFilterInput = '[data-testid=\'filter--input endpoints\']';
const endpointsQuestionMark = '[data-testid=\'Notification Endpoints--question-mark\']';
const endpointsTooltipContents = '[data-testid=\'Notification Endpoints--question-mark-tooltip--contents\']';
const rulesFilterInput = '[data-testid=\'filter--input rules\']';
const rulesQuestionMark = '[data-testid=\'Notification Rules--question-mark\']';
const rulesTooltipContents = '[data-testid=\'Notification Rules--question-mark-tooltip--contents\']';
const firstTimeThresholdCheckCreateButton = '[data-testid=\'checks--column\'] [data-testid=panel--body] [data-testid=button][title=\'Threshold Check\']';
const firstTimeDeadmanCheckCreateButton = '[data-testid=\'checks--column\'] [data-testid=panel--body] [data-testid=button][title=\'Deadman Check\']';
const emptyStateColumnText = '[data-testid=\'%COL%--column\'] [data-testid=\'empty-state--text\']';
//Resource card
const checkCardName = '//*[@data-testid=\'check-card--name\'][./*[text()=\'%NAME%\']]';
const checkCardNameEditButton = '//*[./*/*[text()=\'%NAME%\']]//*[@data-testid=\'check-card--name-button\']';
const checkCardNameInput = '[data-testid=check-card--input]';
const checkCardDescription = '//*[@data-testid=\'check-card\'][.//*[text()=\'%NAME%\']]//*[@data-testid=\'resource-list--editable-description\']';
const checkCardDescriptionEditButton = '//*[@data-testid=\'check-card\'][.//*[text()=\'%NAME%\']]//*[@data-testid=\'resource-list--editable-description\']//*[@data-testid=\'icon\']';
const checkCardDescriptionInput = '[data-testid=check-card] [data-testid=input-field]';
const checkCardAddLabelButton = '//*[@data-testid=\'check-card\'][.//*[text()=\'%NAME%\']]//*[@data-testid=\'inline-labels--add\']';
const checkCardLabelEmpty = '//*[@data-testid=\'check-card\'][.//*[text()=\'%NAME%\']]//*[@data-testid=\'inline-labels--empty\']';
const checkCardLabelPill = '//*[@data-testid=\'check-card\'][.//*[text()=\'%NAME%\']]//*[@data-testid=\'label--pill %LABEL%\']';
const checkCardLabelRemove = '//*[@data-testid=\'check-card\'][.//*[text()=\'%NAME%\']]//*[@data-testid=\'label--pill--delete %LABEL%\']';
const checkCardCloneButton = '//*[@data-testid=\'check-card\'][.//*[text()=\'%NAME%\']]//*[@data-testid=\'context-menu\'][./*[contains(@class,\'duplicate\')]]';
const checkCardCloneConfirm = '//*[@data-testid=\'check-card\'][.//*[text()=\'%NAME%\']]//*[@data-testid=\'context-menu-item\'][text()=\'Clone\']';
const checkCardOpenHistory = '//*[@data-testid=\'check-card\'][.//*[text()=\'%NAME%\']]//*[@data-testid=\'context-menu\'][./*[contains(@class,\'eye-open\')]]';
const checkCardOpenHistoryConfirm = '//*[@data-testid=\'check-card\'][.//*[text()=\'%NAME%\']]//*[@data-testid=\'context-menu-item\']';
const checkCardDeleteButton = '//*[@data-testid=\'check-card\'][.//*[text()=\'%NAME%\']]//*[@data-testid=\'context-delete-menu\']';
const checkCardDeleteConfirm = '//*[@data-testid=\'check-card\'][.//*[text()=\'%NAME%\']]//*[@data-testid=\'context-delete-task\']'
//Create Endpoint Popup
const epPopupEndpointDropdownButton = '[data-testid=endpoint--dropdown--button]';
const epPopupEndpointNameInput = '[data-testid=endpoint-name--input]';
const epPopupEndpointDescriptionText = '[data-testid=endpoint-description--textarea]';
const epPopupCancelButton = '[data-testid=endpoint-cancel--button]';
const epPopupSaveButton = '[data-testid=endpoint-save--button]';
//Query Builder
const qbSelectorListItem = '[data-testid=\'selector-list %ITEM%\']';
const urlCtx = 'alerting';
class alertsPage extends influxPage {
constructor(driver){
super(driver);
}
async isLoaded(){
await super.isLoaded([{type: 'css', selector: pageTitle},
{type: 'css', selector: createCheckButton},
{type: 'css', selector: createEndpointButton},
{type: 'css', selector: createRuleButton},
{type: 'css', selector: checksFilterInput},
{type: 'css', selector: endpointsFilterInput},
{type: 'css', selector: rulesFilterInput},
], urlCtx);
}
async getPageTitle(){
return await this.driver.findElement(By.css(pageTitle));
}
async getAlertingTab(tabName){
return await this.driver.findElement(By.css(alertingTab.replace('%TABNAME%', tabName)));
}
async getCreateCheckButton(){
return await this.driver.findElement(By.css(createCheckButton));
}
async getCreateEndpointButton(){
return await this.driver.findElement(By.css(createEndpointButton));
}
async getCreateRuleButton(){
return await this.driver.findElement(By.css(createRuleButton));
}
async getChecksQuestionMark(){
return await this.driver.findElement(By.css(checksQuestionMark));
}
async getChecksFilterInput(){
return await this.driver.findElement(By.css(checksFilterInput));
}
async getChecksTooltipContents(){
return await this.driver.findElement(By.css(checksTooltipContents));
}
static getChecksTooltipContentsSelector(){
return { type: 'css', selector: checksTooltipContents };
}
async getEndpointsQuestionMark(){
return await this.driver.findElement(By.css(endpointsQuestionMark));
}
async getEndpointsFilterInput(){
return await this.driver.findElement(By.css(endpointsFilterInput));
}
async getEndpointsTooltipContents(){
return await this.driver.findElement(By.css(endpointsTooltipContents));
}
static getEndpointsTooltipContentsSelector(){
return { type: 'css', selector: endpointsTooltipContents };
}
async getRulesFilterInput(){
return await this.driver.findElement(By.css(rulesFilterInput));
}
async getRulesQuestionMark(){
return await this.driver.findElement(By.css(rulesQuestionMark));
}
async getRulesTooltipContents(){
return await this.driver.findElement(By.css(rulesTooltipContents));
}
static getRulesTooltipContentsSelector(){
return { type: 'css', selector: rulesTooltipContents };
}
async getFirstTimeThresholdCheckCreateButton(){
return await this.driver.findElement(By.css(firstTimeThresholdCheckCreateButton));
}
async getFirstTimeDeadmanCheckCreateButton(){
return await this.driver.findElement(By.css(firstTimeDeadmanCheckCreateButton));
}
async getCreateCheckDropdownItem(item){
return await this.driver.findElement(By.css(createCheckDropdownItem.replace('%ITEM%', item.toLowerCase())));
}
static getCreateCheckDropdownSelector(){
return { type: 'css', selector: createCheckDropdown }
}
async getEpPopupEndpointDropdownButton(){
return await this.driver.findElement(By.css(epPopupEndpointDropdownButton));
}
async getEpPopupEndpointNameInput(){
return await this.driver.findElement(By.css(epPopupEndpointNameInput));
}
async getEpPopupEndpointDescriptionText(){
return await this.driver.findElement(By.css(epPopupEndpointDescriptionText));
}
async getEpPopupCancelButton(){
return await this.driver.findElement(By.css(epPopupCancelButton));
}
async getEpPopupSaveButton(){
return await this.driver.findElement(By.css(epPopupSaveButton));
}
async getQbSelectorListItem(item){
return await this.driver.findElement(By.css(qbSelectorListItem.replace('%ITEM%', item)))
}
async getCheckCardName(name){
return await this.driver.findElement(By.xpath(checkCardName.replace('%NAME%', name)));
}
static getCheckCardNameSelector(name){
return {type: 'xpath', selector: checkCardName.replace('%NAME%', name)}
}
async getCheckCardNameEditButton(name){
return await this.driver.findElement(By.xpath(checkCardNameEditButton.replace('%NAME%', name)));
}
async getCheckCardNameInput(){
return await this.driver.findElement(By.css(checkCardNameInput));
}
async getCheckCardDescription(name){
return await this.driver.findElement(By.xpath(checkCardDescription.replace('%NAME%', name)));
}
async getCheckCardDescriptionEditButton(name){
return await this.driver.findElement(By.xpath(checkCardDescriptionEditButton.replace('%NAME%', name)));
}
async getCheckCardDescriptionInput(){
return await this.driver.findElement(By.css(checkCardDescriptionInput));
}
async getCheckCardAddLabelButton(name){
return await this.driver.findElement(By.xpath(checkCardAddLabelButton.replace('%NAME%', name)));
}
async getCheckCardLabelEmpty(name){
return await this.driver.findElement(By.xpath(checkCardLabelEmpty.replace('%NAME%', name)));
}
async getCheckCardLabelPill(name, label){
return await this.driver.findElement(By.xpath(checkCardLabelPill
.replace('%NAME%', name)
.replace('%LABEL%', label)));
}
static getCheckCardLabelPillSelector(name, label){
return { type: 'xpath', selector: checkCardLabelPill
.replace('%NAME%', name)
.replace('%LABEL%', label)}
}
async getCheckCardLabelRemove(name, label){
return await this.driver.findElement(By.xpath(checkCardLabelRemove
.replace('%NAME%', name)
.replace('%LABEL%', label)));
}
async getCheckCardCloneButton(name){
return await this.driver.findElement(By.xpath(checkCardCloneButton.replace('%NAME%', name)))
}
async getCheckCardCloneConfirm(name){
return await this.driver.findElement(By.xpath(checkCardCloneConfirm.replace('%NAME%', name)))
}
async getEmptyStateColumnText(col){
return await this.driver.findElement(By.css(emptyStateColumnText.replace('%COL%', col)));
}
async getCheckCardOpenHistory(name){
return await this.driver.findElement(By.xpath(checkCardOpenHistory.replace('%NAME%', name)))
}
async getCheckCardOpenHistoryConfirm(name){
return await this.driver.findElement(By.xpath(checkCardOpenHistoryConfirm.replace('%NAME%', name)));
}
async getCheckCardDeleteButton(name){
return await this.driver.findElement(By.xpath(checkCardDeleteButton.replace('%NAME%', name)));
}
async getCheckCardDeleteConfirm(name){
return await this.driver.findElement(By.xpath(checkCardDeleteConfirm.replace('%NAME%', name)));
}
}
module.exports = alertsPage;

View File

@ -0,0 +1,198 @@
const influxPage = require(__srcdir + '/pages/influxPage.js');
const { By } = require('selenium-webdriver');
const thresholds = ["CRIT", "WARN", "INFO", "OK"];
const overlay = '[data-testid=overlay]';
const dismissButton = '[data-testid=page-control-bar] [data-testid=square-button]';
const saveCellButton = '[data-testid=overlay] [data-testid=save-cell--button]';
const pageCheckEditTitle = '[data-testid=overlay] [data-testid=page-header] [data-testid=page-title]';
const pageCheckEditTitleInput = '[data-testid=\'page-header\'] [data-testid=\'input-field\']';
const queriesToggle = '[data-testid=overlay] [data-testid=select-group--option][title=queries]';
const configureCheckToggle = '[data-testid=overlay] [data-testid=\'checkeo--header alerting-tab\']';
const defineQueryToggle = '[data-testid=select-group--option]';
const checklistPopover = '[data-testid=popover--contents] [class=query-checklist--popover]';
const checklistPopoverItemByText = '//*[@data-testid=\'popover--contents\']//*[text()=\'%TEXT%\']';
//Preview area
const previewThresholdHandleByLevel = '[class*=\'threshold-marker--handle threshold-marker--%LEVEL%\']';
// TODO timemachine controls -- see dashboards - try and reuse
// Configure Check Controls
// Properties
const confChkIntervalInput = '//*[./label/span[text() = \'Schedule Every\']]//*[@data-testid=\'duration-input\']';
const confChkOffset = '//*[./label/span[text() = \'Offset\']]//*[@data-testid=\'duration-input\']';
const confChkAddTagButton = '//*[./label/span[text() = \'Tags\']]//*[@data-testid=\'dashed-button\']';
// Status Message Template
const confChkMessageTextArea = '[data-testid=status-message-textarea]';
const confTagRuleKeyInputOfTag = '[data-testid=tag-rule]:nth-of-type(%INDEX%) [data-testid=\'tag-rule-key--input\'][name=key]';
const confTagRuleValueInputOfTag = '[data-testid=tag-rule]:nth-of-type(%INDEX%) [data-testid=\'tag-rule-key--input\'][name=value]';
const confTagRuleDimissOfTag = '[data-testid=tag-rule]:nth-of-type(%INDEX%) [data-testid=dismiss-button]';
// Thresholds
const confChkAddThresholdButton = '[data-testid=add-threshold-condition-%STATUS%]';
const confNthThresholdDefDropdownButton = '[data-testid=overlay] [data-testid=panel]:nth-of-type(%INDEX%) [data-testid=select-option-dropdown]';
const confNthThresholdDefDropdownItem = '[data-testid=overlay] [data-testid=panel]:nth-of-type(%INDEX%) [data-testid=select-option-dropdown] [data-testid=dropdown-item][title=\'%ITEM%\']';
const confNthThresholdDefInput = '[data-testid=overlay] [data-testid=panel]:nth-of-type(%INDEX%) [data-testid=input-field]';
const confNthThreshold2ndInput = '[data-testid=overlay] [data-testid=panel]:nth-of-type(%INDEX%) [data-testid^=\'component-spacer--flex-child\']:nth-of-type(3) [data-testid=input-field]';
//Deadman
const confDeadmanForInput = '//*[@data-testid=\'component-spacer\']/*[@data-testid=\'component-spacer\'][.//*[text()=\'for\']]//*[@data-testid=\'duration-input\']';
const confDeadmanForInputDropdownItem = '//*[@data-testid=\'component-spacer\']/*[@data-testid=\'component-spacer\'][.//*[text()=\'for\']]//*[@data-testid=\'dropdown-item\'][.//*[text()=\'%ITEM%\']]';
const confDeadmanStopInput = '//*[@data-testid=\'component-spacer\']/*[@data-testid=\'component-spacer\'][.//*[text()=\'And stop checking after\']]//*[@data-testid=\'duration-input\']';
const confDeadmanStopInputDropdownItem = '//*[@data-testid=\'component-spacer\']/*[@data-testid=\'component-spacer\'][.//*[text()=\'And stop checking after\']]//*[@data-testid=\'dropdown-item\'][.//*[text()=\'%ITEM%\']]';
const confDeadmanCheckLevelsDropdown = '//*[@data-testid=\'component-spacer\']/*[@data-testid=\'component-spacer\']//*[@data-testid=\'check-levels--dropdown--button\']';
const confDeadmanCheckLevelsDropodownItem = '[data-testid=\'check-levels--dropdown-item %LEVEL%\']';
const confDeadmanCheckLevelsDropdownSelected = '[data-testid^=check-levels--dropdown--button] [class*=\'selected\'] [class*=\'dropdown--name\']';
const urlCtx = 'checks';
class checkEditPage extends influxPage {
constructor(driver) {
super(driver);
}
async isLoaded(){
await super.isLoaded([
{type: 'css', selector: dismissButton},
{type: 'css', selector: pageCheckEditTitle},
{type: 'css', selector: queriesToggle},
{type: 'css', selector: configureCheckToggle}
], urlCtx);
}
static getOverlaySelector(){
return { type: 'css', selector: overlay};
}
async getSaveCellButton(){
return await this.driver.findElement(By.css(saveCellButton));
}
async getDismissButton(){
return await this.driver.findElement(By.css(dismissButton));
}
async getPageCheckEditTitle(){
return await this.driver.findElement(By.css(pageCheckEditTitle));
}
async getQueriesToggle(){
return await this.driver.findElement(By.css(queriesToggle));
}
async getConfigureCheckToggle(){
return await this.driver.findElement(By.css(configureCheckToggle))
}
async getDefineQueryToggle(){
return await this.driver.findElement(By.css(defineQueryToggle));
}
async getChecklistPopover(){
return await this.driver.findElement(By.css(checklistPopover));
}
async getPageCheckEditTitleInput(){
return await this.driver.findElement(By.css(pageCheckEditTitleInput));
}
async getConfChkIntervalInput(){
return await this.driver.findElement(By.xpath(confChkIntervalInput));
}
async getConfChkOffset(){
return await this.driver.findElement(By.xpath(confChkOffset));
}
async getConfChkAddTagButton(){
return await this.driver.findElement(By.xpath(confChkAddTagButton));
}
async getConfChkMessageTextArea(){
return await this.driver.findElement(By.css(confChkMessageTextArea));
}
async getConfChkAddThresholdButton(status){
return await this.driver.findElement(By.css(confChkAddThresholdButton.replace('%STATUS%', status)));
}
async getConfNthThresholdDefDropdownButton(index){
return await this.driver.findElement(By.css(confNthThresholdDefDropdownButton
.replace('%INDEX%', await this.getThresholdIndex(index))));
}
async getConfNthThresholdDefDropdownItem(index, item){
return await this.driver.findElement(By.css(confNthThresholdDefDropdownItem
.replace('%INDEX%',await this.getThresholdIndex(index)).replace('%ITEM%', item.toLowerCase())));
}
async getConfNthThresholdDefInput(index){
return await this.driver.findElement(By.css(confNthThresholdDefInput.replace('%INDEX%', await this.getThresholdIndex(index))));
}
async getConfNthThreshold2ndInput(index){
return await this.driver.findElement(By.css(confNthThreshold2ndInput.replace('%INDEX%', await this.getThresholdIndex(index))));
}
async getThresholdIndex(val){
return await thresholds.indexOf(val.toUpperCase().trim()) + 1;
}
async getChecklistPopoverItemByText(text){
return await this.driver.findElement(By.xpath(checklistPopoverItemByText.replace('%TEXT%', text.trim())));
}
async getConfDeadmanForInput(){
return await this.driver.findElement(By.xpath(confDeadmanForInput));
}
async getConfDeadmanForInputDropdownItem(item){
return await this.driver.findElement(By.xpath(confDeadmanForInputDropdownItem.replace('%ITEM%', item)));
}
async getConfDeadmanStopInput(){
return await this.driver.findElement(By.xpath(confDeadmanStopInput));
}
async getConfDeadmanStopInputDropdownItem(item){
return await this.driver.findElement(By.xpath(confDeadmanStopInputDropdownItem.replace('%ITEM%', item)));
}
async getConfDeadmanCheckLevelsDropdown(){
return await this.driver.findElement(By.xpath(confDeadmanCheckLevelsDropdown));
}
async getConfDeadmanCheckLevelsDropodownItem(level){
return await this.driver.findElement(By.css(confDeadmanCheckLevelsDropodownItem.replace('%LEVEL%', level)));
}
async getConfDeadmanCheckLevelsDropdownSelected(){
return await this.driver.findElement(By.css(confDeadmanCheckLevelsDropdownSelected));
}
async getPreviewThresholdHandleByLevel(level){
return await this.driver.findElement(By.css(previewThresholdHandleByLevel.replace('%LEVEL%', level.toLowerCase())));
}
async getConfTagRuleKeyInputOfTag(index){
return await this.driver.findElement(By.css(confTagRuleKeyInputOfTag.replace('%INDEX%', parseInt(index) + 1)));
}
async getConfTagRuleValueInputOfTag(index){
return await this.driver.findElement(By.css(confTagRuleValueInputOfTag.replace('%INDEX%', parseInt(index) + 1)));
}
async getConfTagRuleDimissOfTag(index){
return await this.driver.findElement(By.css(confTagRuleDimissOfTag.replace('%INDEX%', parseInt(index) + 1)));
}
}
module.exports = checkEditPage;

View File

@ -0,0 +1,102 @@
const influxPage = require(__srcdir + '/pages/influxPage.js');
const { By } = require('selenium-webdriver');
const alertHistoryTitle = '[data-testid=alert-history-title]';
const filterInput = '//*[./*[@data-testid=\'input-field--default\']]//*[@data-testid=\'input-field\']';
const eventRows = '.event-row';
const eventRowCheckNameField = '//*[./*[@class=\'event-row\']][%INDEX%]//a';
const eventRowsAtLevel = '//*[./*[@class=\'event-row\']]//*[contains(@class,\'level-table-field--%LEVEL%\')]';
const eventMarkersDiv = '[data-testid=event-markers]';
const eventFilterExamplesDropdown = '[data-testid=dropdown-menu]';
const eventMarkerByIndex = '[data-testid=event-markers] > div:nth-of-type(%INDEX%)';
const eventMarkers = '[data-testid=event-markers] > *';
const eventMarkersByType = '[data-testid=event-markers] > [class*=\'event-marker--line__%TYPE%\'';
const eventMarkerToggleByType = '[data-testid=event-marker-vis-toggle-%TYPE%] > *';
const canvasGraphAxes = 'canvas.giraffe-axes';
const canvasGraphContent = 'canvas.giraffe-layer-line';
const eventTable = '.event-table';
const urlCtx = 'checks';
class checkStatusHistoryPage extends influxPage {
constructor(driver) {
super(driver);
}
async isLoaded() {
await super.isLoaded([{type: 'css', selector: alertHistoryTitle },
{type: 'xpath', selector: filterInput },
{type: 'css', selector: canvasGraphAxes },
{type: 'css', selector: canvasGraphContent },
{type: 'css', selector: eventTable}
], urlCtx);
}
async getAlertHistoryTitle(){
return await this.driver.findElement(By.css(alertHistoryTitle));
}
async getFilterInput(){
return await this.driver.findElement(By.xpath(filterInput));
}
async getEventRows(){
return await this.driver.findElements(By.css(eventRows));
}
async getEventRowCheckNameField(index){
return await this.driver.findElement(By.xpath(eventRowCheckNameField.replace('%INDEX%', index)));
}
async getCanvasGraphAxes(){
return await this.driver.findElement(By.css(canvasGraphAxes));
}
async getCanvasGraphContent(){
return await this.driver.findElement(By.css(canvasGraphContent));
}
async getEventTable(){
return await this.driver.findElement(By.css(eventTable));
}
async getEventRowsAtLevel(level){
return await this.driver.findElements(By.xpath(eventRowsAtLevel.replace('%LEVEL%', level)))
}
async getEventMarkersDiv(){
return await this.driver.findElement(By.css(eventMarkersDiv));
}
async getEventFilterExamplesDropdown(){
return await this.driver.findElement(By.css(eventFilterExamplesDropdown));
}
static getEventFilterExamplesDropdownSelector(){
return { type: 'css', selector: eventFilterExamplesDropdown};
}
async getEventMarkerByIndex(index){
return await this.driver.findElement(By.css(eventMarkerByIndex.replace('%INDEX%', index)));
}
async getEventMarkers(){
return await this.driver.findElements(By.css(eventMarkers));
}
async getEventMarkersByType(){
return await this.driver.findElement(By.css(eventMarkersByType.replace('%TYPE%', type.toLowerCase())));
}
async getEventMarkerToggleByType(type){
return await this.driver.findElement(By.css(eventMarkerToggleByType.replace('%TYPE%', type.toLowerCase())));
}
}
module.exports = checkStatusHistoryPage;

View File

@ -0,0 +1,58 @@
const basePage = require(__srcdir + '/pages/basePage.js');
const { By, until} = require('selenium-webdriver');
const headerMain = '[data-testid=admin-step--head-main]';
const navCrumbToken = 'data-testid=nav-step--';
const inputFieldToken = 'data-testid=input-field--';
const nextButton = '[data-testid=next]';
const formErrorMessage = '[data-testid=form--element-error]';
class initialSetupPage extends basePage {
constructor(driver){
super(driver);
}
async getHeaderMain(){
return await this.driver.findElement(By.css(headerMain));
}
async getCrumbStep(step){
return await this.driver.findElement(By.css(`[${navCrumbToken}${step}]`));
}
async getInputField(name){
return await this.driver.findElement(By.css(`[${inputFieldToken}${name}]`));
}
async getNextButton(){
return this.driver.wait(until.elementLocated(By.css(nextButton)));
//return await this.driver.findElement(By.css(nextButton))
}
async getFormErrorMessage(){
return this.driver.wait(until.elementLocated(By.css(formErrorMessage)));
}
async isFormErrorDisplayed(){
try {
await this.driver.findElement(By.css(formErrorMessage)).isDisplayed();
return true;
}catch(err){
if(err.name === 'NoSuchElementError'){
return false;
}else{
throw err;
}
}
}
async isNextButtonEnabled(){
return await this.driver.findElement(By.css(nextButton)).isEnabled();
}
}
module.exports = initialSetupPage;

View File

@ -0,0 +1,39 @@
const basePage = require(__srcdir + '/pages/basePage.js');
const { By, until} = require('selenium-webdriver');
const subtitle = 'h5.wizard-step--sub-title:first-of-type';
const qStartButton = '[data-testid=button--quick-start]';
const advancedButton = '[data-testid=button--advanced]';
const laterButton = '[data-testid=button--conf-later]';
const urlCtx = '/onboarding/2';
class readyPage extends basePage {
constructor(driver){
super(driver);
}
async getSubtitle(){
return this.driver.wait(until.elementLocated(By.css(subtitle)));
//return await this.driver.findElement(By.css(subtitle))
}
async getQuickStartButton(){
return await this.driver.findElement(By.css(qStartButton));
}
async getAdvancedButton(){
return await this.driver.findElement(By.css(advancedButton));
}
async isLoaded(){
await super.isLoaded([{type:'css', selector:subtitle},
{type:'css', selector:qStartButton},
{type:'css', selector:advancedButton},
{type:'css', selector:laterButton}], urlCtx);
}
}
module.exports = readyPage;

View File

@ -0,0 +1,30 @@
const basePage = require(__srcdir + '/pages/basePage.js');
const { By } = require('selenium-webdriver');
const headMain = '[data-testid=init-step--head-main]';
const creditsLink = '[data-testid=credits] a';
const startButton = '[data-testid=onboarding-get-started]';
class splashPage extends basePage {
constructor(driver){
super(driver);
}
async getHeadMain(){
//promises 201 - passing promisses between methods
//N.B. returns a promise wrapping the element
return await this.driver.findElement(By.css(headMain));
}
async getCreditsLink(){
return await this.driver.findElement(By.css(creditsLink));
}
async getStartButton(){
return await this.driver.findElement(By.css(startButton));
}
}
module.exports = splashPage;

View File

@ -0,0 +1,35 @@
const influxPage = require(__srcdir + '/pages/influxPage.js');
const { By } = require('selenium-webdriver');
const tabsCss = '[data-testid=tabs]';
const tabsXpath = '//*[@data-testid=\'tabs\']';
const urlCtx = 'members';
class organizationPage extends influxPage {
constructor(driver){
super(driver);
}
async isLoaded(){
await super.isLoaded([{type: 'css', selector: tabsCss}], urlCtx);
}
async isTabLoaded(tabUrlPart, selectors = undefined){
if(selectors) {
await super.isLoaded(selectors.concat([{type: 'css', selector: tabsCss}]), tabUrlPart);
}else{
await super.isLoaded([{type: 'css', selector: tabsCss}], tabUrlPart);
}
}
async getTabByName(name){
return await this.driver.findElement(By.xpath(`${tabsXpath}//a[@data-testid='tabs--tab'][.//*[text()='${name}']]`));
}
//`//div[@class='site-bottom-wrapper'][descendant::a[contains(.,'${ siteName }')]]//div[contains(@class, 'manage-settings')]`
}
module.exports = organizationPage;

View File

@ -0,0 +1,140 @@
const { By } = require('selenium-webdriver');
const settingsPage = require(__srcdir + '/pages/settings/settingsPage.js');
const basePage = require(__srcdir + '/pages/basePage.js');
const labelsFilter = '[data-testid=search-widget]';
const createLabelHeader = '[data-testid=button-create]';
const labelNameSort = '[data-testid=sorter--name]';
const labelDescSort = '[data-testid=sorter--desc]';
const createLabelEmpty = '[data-testid=button-create-initial]';
const labelCard = '//*[@data-testid=\'label-card\'][.//span[text()=\'%NAME%\']]';
const labelCardPills = '[data-testid^=label--pill]';
const labelCardPill = '//*[@data-testid=\'label-card\']//div[./span[@data-testid=\'label--pill %NAME%\']]';
const labelCardDescr = '//*[@data-testid=\'label-card\'][.//span[text()=\'%NAME%\']]//*[@data-testid=\'label-card--description\']';
const labelCardDelete = '//*[@data-testid=\'label-card\'][.//span[text()=\'%NAME%\']]//button[@data-testid=\'context-delete-menu\']';
const labelCardDeleteConfirm = '//*[@data-testid=\'label-card\'][.//span[text()=\'%NAME%\']]//button[@data-testid=\'context-delete-label\']';
const urlCtx = 'labels';
//Create Label Popup
const labelPopupNameInput = '[data-testid=create-label-form--name]';
const labelPopupDescrInput = '[data-testid=create-label-form--description]';
const labelPopupColorPicker = '[data-testid=color-picker]';
const labelPopupColorInput = '[data-testid=color-picker--input]';
const labelPopupCreateBtn = '[data-testid=create-label-form--submit]';
const labelPopupCancelBtn = '[data-testid=create-label-form--cancel]';
const labelPopupPreview = '[data-testid=overlay--body] [data-testid=form--box] div';
const labelPopupPreviewPill = '[data-testid=overlay--body] [data-testid^=label--pill]';
const labelPopupRandomColor = '[data-testid=color-picker--randomize]';
const labelPopupColorSwatch = '[data-testid=color-picker--swatch][title=\'%NAME%\']';
class labelsTab extends settingsPage{
constructor(driver){
super(driver);
}
async isTabLoaded(){
await super.isTabLoaded(urlCtx,
[
{type: 'css', selector: labelsFilter},
{type: 'css', selector: createLabelHeader},
basePage.getSortTypeButtonSelector()
//{type: 'css', selector: labelNameSort},
//{type: 'css', selector: labelDescSort},
]
);
}
async getLabelNameSort(){
return await this.driver.findElement(By.css(labelNameSort));
}
async getLabelDescSort(){
return await this.driver.findElement(By.css(labelDescSort));
}
async getLabelsFilter(){
return await this.driver.findElement(By.css(labelsFilter));
}
async getCreateLabelHeader(){
return await this.driver.findElement(By.css(createLabelHeader));
}
async getCreateLabelEmpty(){
return await this.driver.findElement(By.css(createLabelEmpty));
}
async getLabelPopupNameInput(){
return await this.driver.findElement(By.css(labelPopupNameInput));
}
async getLabelPopupDescrInput(){
return await this.driver.findElement(By.css(labelPopupDescrInput));
}
async getLabelPopupColorPicker(){
return await this.driver.findElement(By.css(labelPopupColorPicker));
}
async getLabelPopupColorInput(){
return await this.driver.findElement(By.css(labelPopupColorInput));
}
async getLabelPopupCreateBtn(){
return await this.driver.findElement(By.css(labelPopupCreateBtn));
}
async getLabelPopupCancelBtn(){
return await this.driver.findElement(By.css(labelPopupCancelBtn));
}
async getLabelPopupPreview(){
return await this.driver.findElement(By.css(labelPopupPreview));
}
async getLabelPopupPreviewPill(){
return await this.driver.findElement(By.css(labelPopupPreviewPill));
}
async getLabelPopupRandomColor(){
return await this.driver.findElement(By.css(labelPopupRandomColor));
}
async getLabelPopupColorSwatch(name){
return await this.driver.findElement(By.css(labelPopupColorSwatch.replace('%NAME%', name)));
}
async getLabelCard(name){
return await this.driver.findElement(By.xpath(labelCard.replace('%NAME%', name)));
}
static getLabelCardSelector(name){
return { type: 'xpath', selector: labelCard.replace('%NAME%', name)};
}
async getLabelCardPill(name){
return await this.driver.findElement(By.xpath(labelCardPill.replace('%NAME%', name)));
}
async getLabelCardPills(){
return await this.driver.findElements(By.css(labelCardPills));
}
async getLabelCardDescr(name){
return await this.driver.findElement(By.xpath(labelCardDescr.replace('%NAME%', name)));
}
async getLabelCardDelete(name){
return await this.driver.findElement(By.xpath(labelCardDelete.replace('%NAME%', name)));
}
async getLabelCardDeleteConfirm(name){
return await this.driver.findElement(By.xpath(labelCardDeleteConfirm.replace('%NAME%', name)));
}
}
module.exports = labelsTab;

View File

@ -0,0 +1,25 @@
const settingsPage = require(__srcdir + '/pages/settings/settingsPage.js');
const variablesFilter = '[data-testid=search-widget]';
//const addMemberButton = '[data-testid=flex-box] [data-testid=button]';
const urlCtx = 'members';
class membersTab extends settingsPage{
constructor(driver){
super(driver);
}
async isTabLoaded(){
await super.isTabLoaded(urlCtx,
[
{type: 'css', selector: variablesFilter},
// {type: 'css', selector: addMemberButton},
]
);
}
}
module.exports = membersTab;

View File

@ -0,0 +1,27 @@
const settingsPage = require(__srcdir + '/pages/settings/settingsPage.js');
const tabHeader = '//*[@data-testid=\'flex-box\'][.//h5]';
const warningHeader = '//*[@data-testid=\'flex-box\']//p';
const renameButton = '[data-testid=button][title=\'Rename\']';
const urlCtx = 'about';
class orgProfileTab extends settingsPage{
constructor(driver){
super(driver);
}
async isTabLoaded(){
await super.isTabLoaded(urlCtx,
[
{type: 'xpath', selector: tabHeader},
{type: 'xpath', selector: warningHeader},
{type: 'css', selector: renameButton},
]
);
}
}
module.exports = orgProfileTab;

View File

@ -0,0 +1,33 @@
const influxPage = require(__srcdir + '/pages/influxPage.js');
const { By } = require('selenium-webdriver');
const tabsCss = '[data-testid=tabs]';
const tabsXpath = '//*[@data-testid=\'tabs\']';
const urlCtx = 'settings';
class settingsPage extends influxPage {
constructor(driver){
super(driver);
}
async isLoaded(){
await super.isLoaded([{type: 'css', selector: tabsCss}], urlCtx);
}
async isTabLoaded(tabUrlPart, selectors = undefined){
if(selectors) {
await super.isLoaded(selectors.concat([{type: 'css', selector: tabsCss}]), tabUrlPart);
}else{
await super.isLoaded([{type: 'css', selector: tabsCss}], tabUrlPart);
}
}
async getTabByName(name){
return await this.driver.findElement(By.xpath(`${tabsXpath}//div[@data-testid='tabs--tab' and @id='${name.toLowerCase()}']`));
}
}
module.exports = settingsPage;

View File

@ -0,0 +1,110 @@
const { By } = require('selenium-webdriver');
const settingsPage = require(__srcdir + '/pages/settings/settingsPage.js');
const templatesFilter = '[data-testid=search-widget]';
const importTemplateHeaderButton = '//*[@data-testid=\'button\'][.//*[text()=\'Import Template\']]';
const sortTypeButton = '[data-testid=resource-sorter--button]';
const nameSort = '[data-testid=resource-list--sorter]:nth-of-type(1)';
const templatesTypeFilterButton = '[data-testid=select-group--option][title=\'Static Templates\']';
const userTemplatesRadioButton = '[data-testid=select-group--option][title=\'User Templates\']';
const resourceList = '[data-testid=resource-list]';
const templateCardNames = '[data-testid=template-card--name]';
const importTemplateEmptyButton = '[data-testid=empty-state] [data-testid=button]';
const templateCardByName = '//*[@data-testid=\'template-card\'][.//*[text() = \'%NAME%\']]';
const templateCardCtxDelete = '//*[@data-testid=\'template-card\'][.//*[text() = \'%NAME%\']]//*[@data-testid=\'context-delete-menu\']';
const templateCardDeleteConfirm = '//*[@data-testid=\'template-card\'][.//*[text() = \'%NAME%\']]//*[@data-testid=\'context-delete-task\']';
const urlCtx = 'templates';
//import template popup
const importTemplateUploadButton = '[data-testid=overlay--body] [data-testid=select-group--option][title=Upload]';
const importTemplatePasteButton = '[data-testid=overlay--body] [data-testid=select-group--option][title=Paste]';
const importTemplateJSONTextArea = '[data-testid=overlay--body] [data-testid=import-overlay--textarea]';
const importTemplateDragNDrop = '[data-testid=overlay--body] input[type=file]';
class templatesTab extends settingsPage{
constructor(driver){
super(driver);
}
async isTabLoaded(){
await super.isTabLoaded(urlCtx,
[
{type: 'css', selector: templatesFilter},
{type: 'xpath', selector: importTemplateHeaderButton},
{type: 'css', selector: sortTypeButton},
{type: 'css', selector: templatesTypeFilterButton},
{type: 'css', selector: resourceList},
]
);
}
async getTemplateCardNames(){
return await this.driver.findElements(By.css(templateCardNames));
}
async getUserTemplatesRadioButton(){
return await this.driver.findElement(By.css(userTemplatesRadioButton));
}
async getImportTemplateEmptyButton(){
return await this.driver.findElement(By.css(importTemplateEmptyButton));
}
async getImportTemplateUploadButton(){
return await this.driver.findElement(By.css(importTemplateUploadButton));
}
async getImportTemplatePasteButton(){
return await this.driver.findElement(By.css(importTemplatePasteButton));
}
async getImportTemplateHeaderButton(){
return await this.driver.findElement(By.xpath(importTemplateHeaderButton));
}
async getImportTemplateJSONTextArea(){
return await this.driver.findElement(By.css(importTemplateJSONTextArea));
}
static getImportTemplateJSONTextAreaSelector(){
return { type: 'css', selector: importTemplateJSONTextArea};
}
async getImportTemplateDragNDrop(){
return await this.driver.findElement(By.css(importTemplateDragNDrop));
}
async getTemplateCardByName(name){
return await this.driver.findElement(By.xpath(templateCardByName.replace('%NAME%', name)));
}
static getTemplateCardSelectorByName(name){
return { type: 'xpath', selector: templateCardByName.replace('%NAME%', name) };
}
async getTemplatesFilter(){
return await this.driver.findElement(By.css(templatesFilter));
}
async getSortTypeButton(){
return await this.driver.findElement(By.css(sortTypeButton));
}
async getNameSort(){
return await this.driver.findElement(By.css(nameSort));
}
async getTemplateCardCtxDelete(name){
return await this.driver.findElement(By.xpath(templateCardCtxDelete.replace('%NAME%', name)));
}
async getTemplateCardDeleteConfirm(name){
return await this.driver.findElement(By.xpath(templateCardDeleteConfirm.replace('%NAME%', name)));
}
}
module.exports = templatesTab;

View File

@ -0,0 +1,250 @@
const { By } = require('selenium-webdriver');
const settingsPage = require(__srcdir + '/pages/settings/settingsPage.js');
const basePage = require(__srcdir + '/pages/basePage.js');
const variablesFilter = '[data-testid=search-widget]';
const createVariableHeader = '[data-testid=\'tabbed-page--header\'] [data-testid=add-resource-dropdown--button]';
const nameSort = '[data-testid=resource-list--sorter]:nth-of-type(1)';
const typeSort = '[data-testid=resource-list--sorter]:nth-of-type(2)';
const createVariableEmpty = '[data-testid=resource-list--body] [data-testid=add-resource-dropdown--button]';
const createVariableItem = '[data-testid=add-resource-dropdown--%ITEM%]';
const variableCardNamed = '//*[@data-testid=\'resource-card\'][.//*[text()=\'%NAME%\']]';
const variableCardNames = '//*[@data-testid=\'resource-name\']/span';
const variableCardName = '//*[@data-testid=\'resource-name\']//span[text()=\'%NAME%\']';
const variableCardContextMenu = '//*[@data-testid=\'resource-card\'][.//span[text()=\'%NAME%\']]//*[@data-testid=\'context-menu\']';
const variableCardContextMenuItem = '//*[@data-testid=\'resource-card\'][.//span[text()=\'%NAME%\']]//*[button[@data-testid=\'context-menu\']]//button[text()=\'%ITEM%\']';
const variableCardContextDelete = '//*[@data-testid=\'resource-card\'][.//span[text() = \'%NAME%\']]//*[@data-testid=\'context-delete-menu\']';
const variableCardContextDeleteConfirm = '//*[@data-testid=\'resource-card\'][.//span[text() = \'%NAME%\']]//*[@data-testid=\'context-delete-variable\']';
const urlCtx = 'variables';
// import variable popup
const uploadRadioButton = '[data-testid=overlay--body] [data-testid=select-group--option][title=Upload]';
const pasteRadioButton = '[data-testid=overlay--body] [data-testid=select-group--option][title=Paste]';
const dragNDropFile = 'input[type=file]'; //N.B. has display:none
const importButton = '[data-testid=overlay--footer] [data-testid=button]';
const pasteJSONTextarea = '[data-testid=overlay--body] [data-testid=import-overlay--textarea]';
const importVariableDragNDropHeader = '.drag-and-drop--header';
// create variable popup
const createVarPopupCreateButton = '[data-testid=overlay--container] button[title=\'Create Variable\']';
const createVariableNameInput = '[data-testid=overlay--body] [data-testid=input-field]';
const createVariableTypeDropdown = '[data-testid=\'variable-form--dropdown-button\']';
const createVariableQueryCodeMirror = '.CodeMirror';
const createVariableQueryMonacoEdit = '.monaco-editor';
const createVariableTextArea = '[data-testid=overlay--body] [data-testid=textarea]';
const createVariableTypeDropdownItem = '[data-testid=\'variable-form--dropdown-%ITEM%\']';
const createVariableDefaultValDropdown = '//*[@data-testid=\'form--element\'][label/span[text() = \'Select A Default\']]//*[@data-testid=\'dropdown--button\']';
const createVariableInfoPara = '//*[@data-testid=\'grid--column\'][p[contains(text(), \'ontains\')]]';
const createVariableDefaultValDropdownItem = '[data-testid=dropdown-item][id=\'%ITEM%\']';
const createVariableDefaultValCSVDropdownItem = '//*[@data-testid=\'dropdown-item\']//*[text() = \'%ITEM%\']';
// edit variable popup
const editVariableTypeDropdown = '//*[@data-testid=\'form--element\'][.//span[text() = \'Type\']]//*[@data-testid=\'dropdown--button\']';
const editVariableTypeDropdownItem = '//*[@data-testid=\'form--element\'][.//span[text() = \'Type\']]//*[@data-testid=\'dropdown-item\'][@id=\'%ITEM%\']';
const editVariableNameInput = '//*[@data-testid=\'form--element\'][.//span[text() = \'Name\']]//input';
const editWarnVariablSubmit = '[data-testid=danger-confirmation-button]';
const editVariableNameChangeSubmit = '[data-testid=rename-variable-submit]';
//Warning popup
const updateNameNameInput = '[data-testid=overlay--body] [data-testid=rename-variable-input]';
class variablesTab extends settingsPage{
constructor(driver){
super(driver);
}
async isTabLoaded(){
await super.isTabLoaded(urlCtx,
[
{type: 'css', selector: variablesFilter},
{type: 'css', selector: createVariableHeader},
basePage.getSortTypeButtonSelector()
//{type: 'css', selector: nameSort},
//{type: 'css', selector: typeSort},
]
);
}
async getVariablesFilter(){
return await this.driver.findElement(By.css(variablesFilter));
}
async getCreateVariableHeader(){
return await this.driver.findElement(By.css(createVariableHeader));
}
async getCreateVariableEmpty(){
return await this.driver.findElement(By.css(createVariableEmpty));
}
async getCreateVariableItem(item){
return await this.driver.findElement(By.css(createVariableItem.replace('%ITEM%', item)));
}
async getPasteRadioButton(){
return await this.driver.findElement(By.css(pasteRadioButton));
}
async getUploadRadioButton(){
return await this.driver.findElement(By.css(uploadRadioButton));
}
async getDragNDropFile(){
return await this.driver.findElement(By.css(dragNDropFile));
}
static getDragNDropFileSelector(){
return { type: 'css', selector: dragNDropFile};
}
async getImportButton(){
return await this.driver.findElement(By.css(importButton));
}
async getCreateVariableNameInput(){
return await this.driver.findElement(By.css(createVariableNameInput));
}
async getCreateVariableTypeDropdown(){
return await this.driver.findElement(By.css(createVariableTypeDropdown));
}
async getCreateVariableQueryCodeMirror(){
return await this.driver.findElement(By.css(createVariableQueryCodeMirror));
}
static getCreateVariableQueryCodeMirrorSelector(){
return {type: 'css', selector: createVariableQueryCodeMirror };
}
async getCreateVariableQueryMonacoEdit(){
return await this.driver.findElement(By.css(createVariableQueryMonacoEdit));
}
static getCreateVariableQueryMonacoEditSelector(){
return {type: 'css', selector: createVariableQueryMonacoEdit};
}
async getCreateVariableTextArea(){
return await this.driver.findElement(By.css(createVariableTextArea));
}
static getCreateVariableTextAreaSelector(){
return {type: 'css', selector: createVariableTextArea};
}
async getPasteJSONTextarea(){
return await this.driver.findElement(By.css(pasteJSONTextarea));
}
static getPasteJSONTextareaSelector(){
return { type: 'css', selector: pasteJSONTextarea};
}
async getCreateVariableTypeDropdownItem(item){
return await this.driver.findElement(By.css(createVariableTypeDropdownItem
.replace('%ITEM%', item.toLowerCase())));
}
async getCreateVariableDefaultValDropdown(){
return await this.driver.findElement(By.xpath(createVariableDefaultValDropdown));
}
static getCreateVariableDefaultValDropdownSelector(){
return { type: 'xpath', selector: createVariableDefaultValDropdown};
}
async getCreateVariableInfoPara(){
return await this.driver.findElement(By.xpath(createVariableInfoPara));
}
static getCreateVariableInfoParaSelector(){
return { type: 'xpath', selector: createVariableInfoPara};
}
async getImportVariableDragNDropHeader(){
return await this.driver.findElement(By.css(importVariableDragNDropHeader));
}
async getVariableCardNamed(name){
return await this.driver.findElement(By.xpath(variableCardNamed.replace('%NAME%', name)));
}
static getVariableCardSelectorByName(name){
return { type: 'xpath', selector: variableCardNamed.replace('%NAME%', name)};
}
async getCreateVariableDefaultValDropdownItem(item){
return await this.driver.findElement(By.css(createVariableDefaultValDropdownItem.replace('%ITEM%', item)));
}
async getCreateVariableDefaultValCSVDropdownItem(item){
return await this.driver.findElement(By.xpath(createVariableDefaultValCSVDropdownItem.replace('%ITEM%', item)));
}
async getVariableCardNames(){
return await this.driver.findElements(By.xpath(variableCardNames));
}
async getVariableCardName(name){
return await this.driver.findElement(By.xpath(variableCardName.replace('%NAME%', name)));
}
async getNameSort(){
return await this.driver.findElement(By.css(nameSort));
}
async getVariableCardContextMenu(name){
return await this.driver.findElement(By.xpath(variableCardContextMenu.replace('%NAME%', name)));
}
async getVariableCardContextMenuItem(name, item){
return await this.driver.findElement(By.xpath(variableCardContextMenuItem
.replace('%NAME%', name)
.replace('%ITEM%', item)));
}
async getUpdateNameNameInput(){
return await this.driver.findElement(By.css(updateNameNameInput));
}
async getEditVariableTypeDropdown(){
return await this.driver.findElement(By.xpath(editVariableTypeDropdown));
}
async getEditVariableTypeDropdownItem(item){
return await this.driver.findElement(By.xpath(editVariableTypeDropdownItem.replace('%ITEM%', item)));
}
async getEditVariableNameInput(){
return await this.driver.findElement(By.xpath(editVariableNameInput));
}
async getVariableCardContextDelete(name){
return await this.driver.findElement(By.xpath(variableCardContextDelete.replace('%NAME%', name)));
}
async getVariableCardContextDeleteConfirm(name){
return await this.driver.findElement(By.xpath(variableCardContextDeleteConfirm.replace('%NAME%', name)));
}
async getEditVariablWarnSubmit(){
return await this.driver.findElement(By.css(editWarnVariablSubmit));
}
async getEditVariableNameChangeSubmit(){
return await this.driver.findElement(By.css(editVariableNameChangeSubmit));
}
async getCreateVarPopupCreateButton(){
return await this.driver.findElement(By.css(createVarPopupCreateButton));
}
}
module.exports = variablesTab;

View File

@ -0,0 +1,51 @@
const basePage = require(__srcdir + '/pages/basePage.js');
const { By } = require('selenium-webdriver');
const heading = '.splash-page--heading';
const influxLogo = '[data-testid=logo--influxdb-cloud]';
const versionInfo = '.version-info p';
const creditsLink = '.splash-page--credits a';
const nameInput = 'input[name=username]'; //TODO - see if data-testid can be updated - currently 'input-field' for both name and password inputds
const passwordInput = 'input[name=password]'; //TODO - see if data-testid can be updated
const signinButton = '[data-testid=button]';
const urlCtx = 'signin';
class signinPage extends basePage{
constructor(driver){
super(driver);
this.urlCtx = urlCtx;
}
async getHeading(){
return await this.driver.findElement(By.css(heading));
}
async getVersionInfo(){
return await this.driver.findElement(By.css(versionInfo));
}
async getCreditsLink(){
return await this.driver.findElement(By.css(creditsLink));
}
async getNameInput(){
return await this.driver.findElement(By.css(nameInput));
}
async getPasswordInput(){
return await this.driver.findElement(By.css(passwordInput));
}
async getSigninButton(){
return await this.driver.findElement(By.css(signinButton));
}
async getInfluxLogo(){
return await this.driver.findElement(By.css(influxLogo));
}
}
module.exports = signinPage;

View File

@ -0,0 +1,70 @@
const influxPage = require(__srcdir + '/pages/influxPage.js');
const basePage = require(__srcdir + '/pages/basePage.js');
const { By } = require('selenium-webdriver');
const filterTasks = '[data-testid=search-widget]';
const inactiveToggle = '[data-testid=slide-toggle]';
const createTaskDropdownHeader = '[data-testid=page-control-bar--right] [data-testid=add-resource-dropdown--button]';
const nameSortButton = '[data-testid=resource-list--sorter]:nth-of-type(1)';
const activeSortButton = '[data-testid=resource-list--sorter]:nth-of-type(2)';
const scheduleSortButton = '[data-testid=resource-list--sorter]:nth-of-type(3)';
const lastCompletedSortButton = '[data-testid=resource-list--sorter]:nth-of-type(4)';
// following is only present until first task is created, so not good candidate for isLoaded check
const createTaskDropdownBody = '[data-testid=resource-list--body] [data-testid=add-resource-dropdown--button]';
const urlCtx = 'tasks';
class tasksPage extends influxPage{
constructor(driver){
super(driver);
}
async isLoaded(){
await super.isLoaded([{type: 'css', selector: filterTasks},
{type: 'css', selector: inactiveToggle} ,
{type: 'css', selector: createTaskDropdownHeader} ,
// {type: 'css', selector: nameSortButton},
basePage.getSortTypeButtonSelector(),
//{type: 'css', selector: activeSortButton},
//{type: 'css', selector: scheduleSortButton},
//{type: 'css', selector: lastCompletedSortButton}
], urlCtx);
}
async getFilterTasks(){
return await this.driver.findElement(By.css(filterTasks));
}
async getInactiveToggle(){
return await this.driver.findElement(By.css(inactiveToggle));
}
async getCreateTaskDropdownHeader(){
return await this.driver.findElement(By.css(createTaskDropdownHeader));
}
async getNameSortButton(){
return await this.driver.findElement(By.css(nameSortButton));
}
async getActiveSortButton(){
return await this.driver.findElement(By.css(activeSortButton));
}
async getScheduleSortButton(){
return await this.driver.findElement(By.css(scheduleSortButton));
}
async getLastCompetedSortButton(){
return await this.driver.findElement(By.css(lastCompletedSortButton));
}
async getCreateTaskDropdownBody(){
return await this.driver.findElement(By.css(createTaskDropdownBody));
}
}
module.exports = tasksPage;

View File

@ -0,0 +1,372 @@
import { AfterAll, Given, Then, When } from 'cucumber';
import {flush} from '../../utils/influxUtils';
const baseSteps = require(__srcdir + '/steps/baseSteps.js');
const signinSteps = require(__srcdir + '/steps/signin/signinSteps.js');
const influxSteps = require(__srcdir + '/steps/influx/influxSteps.js');
const influxUtils = require(__srcdir + '/utils/influxUtils.js');
let bSteps = new baseSteps(__wdriver);
let sSteps = new signinSteps(__wdriver);
let iSteps = new influxSteps(__wdriver);
Given(/^I reset the environment$/, async () => {
await bSteps.driver.sleep(1000); //since gets called after scenarios, need a short delay to avoid promise resolution issues
await flush();
});
/*
Before(() => {
})
BeforeAll(() => {
})
After(() => {
console.log("DEBUG After hook")
})
*/
AfterAll(async() => {
await bSteps.driver.close();
});
When(/^clear browser storage$/, async () => {
await bSteps.clearBrowserLocalStorage();
});
Then(/^the success notification says "(.*?)"$/, async message => {
await bSteps.isNotificationMessage(message);
});
Then(/^the success notification contains "(.*?)"$/, async text => {
//can be used in template or outline - some cases may needed to be skipped
if(text.toLowerCase() !== 'skip') {
await bSteps.containsNotificationText(text);
}
});
Then(/^the primary notification contains "(.*)"$/, async text => {
if(text.toLowerCase() !== 'skip') {
await bSteps.containsPrimaryNotificationText(text);
}
});
Then(/^the error notification contains "(.*?)"$/, async text => {
await bSteps.containsErrorNotificationText(text);
});
When(/^close all notifications$/, async() => {
await bSteps.closeAllNotifications();
});
// newUser if not DEFAULT should follow {username: 'name', password: 'password', org: 'org', bucket: 'bucket'}
Given(/^run setup over REST "(.*?)"$/, async( newUser ) => {
await influxUtils.flush();
if(newUser === 'DEFAULT'){
await influxUtils.setupUser(__defaultUser);
}else{
let user = JSON.parse(newUser);
if(user.password.length < 8 ){
throw Error(`Password: ${user.password} is shorter than 8 chars`);
}
await influxUtils.setupUser(user);
}
});
When(/^API sign in user "(.*?)"$/, async username => {
await influxUtils.signIn((username === 'DEFAULT') ? __defaultUser.username : username).then(async () => {
// await sSteps.driver.sleep(1500)
}).catch(async err => {
console.log('ERROR ' + err);
throw(err);
});
});
When(/^API end session$/, async() => {
await influxUtils.endSession();
});
When(/^UI sign in user "(.*?)"$/, {timeout: 10000}, async username => {
let user = influxUtils.getUser((username === 'DEFAULT') ? __defaultUser.username : username);
await sSteps.signin(user);
//await sSteps.driver.sleep(1500)
});
When(/^write basic test data for org "(.*?)" to bucket "(.*?)"$/, async (org,bucket) => {
await influxUtils.writeData(org,bucket);
});
When(/^write sine data for org "(.*?)" to bucket "(.*?)"$/, async (org, bucket) =>{
let nowNano = new Date().getTime() * 1000000;
let intervalNano = 600 * 1000 * 1000000; //10 min in nanosecs
let lines = [];
let recCount = 256;
let startTime = nowNano - (recCount * intervalNano);
for(let i = 0; i < recCount; i++){
lines[i] = 'sinus point=' + Math.sin(i) + ' ' + (startTime + (i * intervalNano));
}
console.log('DEBUG lines: ');
lines.forEach((line) => {
console.log(line);
});
await influxUtils.writeData(org, bucket, lines);
});
When(/^query sine data for org of user "(.*)" from bucket "(.*)"$/, async (user, bucket) => {
let startTime = '-1d';
let org = influxUtils.getUser(user).orgid;
let query = `from(bucket: "${bucket}")
|> range(start: ${startTime})
|> filter(fn: (r) => r._measurement == "sinus")
|> filter(fn: (r) => r._field == "point")`;
let results = await influxUtils.query(org, query);
console.log('DEBUG results: ' + results);
});
When(/^API create a dashboard named "(.*?)" for user "(.*?)"$/, async (name, username) => {
let user = await influxUtils.getUser(username);
let dashb = await influxUtils.createDashboard(name, user.orgid);
if(user.dashboards === undefined){
user.dashboards = new Object();
}
//Save dashboard for later use
user.dashboards[name] = dashb;
});
When(/^API create a bucket named "(.*)" for user "(.*)"$/, async (bucket, username) => {
let user = await influxUtils.getUser((username === 'DEFAULT') ? __defaultUser.username : username);
await influxUtils.createBucket(user.orgid, user.org, bucket);
});
When(/^API create a label "(.*)" described as "(.*)" with color "(.*)" for user "(.*)"$/,
async (labelName, labelDescr, labelColor, user) => {
let orgID = influxUtils.getUser((user === 'DEFAULT') ? __defaultUser.username : user).orgid;
await influxUtils.createLabel(orgID, labelName, labelDescr, labelColor);
});
When(/^open page "(.*?)" for user "(.*?)"$/, async (page, username) => {
let user = await influxUtils.getUser((username === 'DEFAULT') ? __defaultUser.username : username);
let ctx = 'orgs/' + user.orgid;
if(page !== 'HOME'){
ctx += `/${page}`;
}
await bSteps.openContext(ctx);
await iSteps.isLoaded();
//await bSteps.driver.sleep(3000)
});
Then(/^the form element error message is "(.*)"$/, async msg => {
await bSteps.verifyElementErrorMessage(msg);
});
Then(/^the form element error message is not shown$/, async () => {
await bSteps.verifyNoElementErrorMessage();
});
Then(/^no form input error icon is shown$/, async () => {
await bSteps.verifyNoFormInputErrorIcon();
});
Then(/^a form input error icon is shown$/, async () => {
await bSteps.verifyInputErrorIcon();
});
When(/^click the Popup Wizard continue button$/, {timeout: 15000}, async() => {
await bSteps.clickPopupWizardContinue();
//await bSteps.driver.sleep(10000);
});
When(/^click the wizard previous button$/, async () => {
await bSteps.clickPopupWizardPrevious();
});
When(/^click the Popup Wizard done button$/, async () => {
await bSteps.clickPopupWizardContinue();
});
Then(/^the popup wizard continue button is disabled$/, async() => {
await bSteps.verifyWizardContinueButtonDisabled();
});
When(/^dismiss the popup$/, async () => {
await bSteps.dismissPopup();
});
When(/^click popup cancel button$/, async () => {
await bSteps.clickPopupCancelBtn();
});
When(/^click popup cancel simple button$/, async () => {
await bSteps.clickPopupCancelBtnSimple();
});
Then(/^popup is not loaded$/, async () => {
await bSteps.verifyPopupNotPresent();
});
When(/^click popup submit button$/, async () => {
await bSteps.clickPopupSubmitButton();
});
Then(/^the popup wizard step state text contains "(.*)"$/, async text => {
await bSteps.verifyPopupWizardStepStateText(text);
});
Then(/^the popup wizard step is in state "(.*)"$/, async state => {
await bSteps.verifyPopupWizardStepState(state);
});
Then(/^the popup wizard import file header contains "(.*)"$/, async text => {
await bSteps.verifyPopupFileUploadHeaderText(text);
});
When(/^generate a line protocol testdata file "(.*)" based on:$/, async (filePath, def) => {
await influxUtils.genLineProtocolFile(filePath, def);
});
When(/^generate a line protocol testdata for user "(.*)" based on:$/, async (user, def) => {
await influxUtils.writeLineProtocolData((user === 'DEFAULT')? __defaultUser: await influxUtils.getUser(user),
def);
});
When(/^create the "(.*)" variable "(.*)" with default "(.*)" for user "(.*)" with values:$/,
async(type, name, defVal, user, values) => {
type = type === 'csv' ? 'constant' : type.toLowerCase();
let orgID = influxUtils.getUser((user === 'DEFAULT') ? __defaultUser.username : user).orgid;
await influxUtils.createVariable(orgID, name, type, values, defVal)
});
//For troubleshooting - up to 5 min
When(/^wait "(.*)" seconds$/, {timeout: 5 * 60 * 1000}, async secs => {
await bSteps.driver.sleep(parseInt(secs) * 1000);
});
When(/^force page refresh$/, async ()=> {
await bSteps.driver.navigate().refresh();
});
When(/^press the "(.*)" key$/, async key => {
await bSteps.pressKeyAndWait(key);
});
When(/^create a new template from the file "(.*)" for user "(.*)"$/, async (filepath, user) => {
let orgID = influxUtils.getUser((user === 'DEFAULT') ? __defaultUser.username : user).orgid;
await influxUtils.createTemplateFromFile(filepath, orgID);
});
When(/^create check over API from file "(.*)" for user "(.*)"$/, async (filepath, user) => {
let orgID = influxUtils.getUser((user === 'DEFAULT') ? __defaultUser.username : user).orgid;
await influxUtils.createAlertCheckFromFile(filepath, orgID);
});
When(/^remove file "(.*)" if exists$/, async filePath => {
await influxUtils.removeFileIfExists(filePath);
});
Then(/^the file "(.*)" has been downloaded$/, async filePath => {
await bSteps.verifyFileExists(filePath);
});
When(/^remove files "(.*)" if exists$/, async regex => {
await influxUtils.removeFilesByRegex(regex);
});
Then(/^a file matching "(.*)" exists$/, async regex => {
await bSteps.verifyFileMatchingRegexExists(regex);
});
When(/^verify first CSV file matching "(.*)" as containing$/, async (path, dataDesc) => {
let datdescr = JSON.parse(dataDesc);
await bSteps.verifyFirstCSVFileMatching(path, datdescr);
});
When(/^get console log$/, async () => {
await bSteps.getConsoleLog();
});
When(/^write message "(.*)" to console log$/, async msg => {
await bSteps.writeMessageToConsoleLog(msg);
});
When(/^send keys "(.*)"$/, async keys => {
await bSteps.sendKeysToCurrent(keys);
});
When(/^start live data generator$/, async def => {
bSteps.startLiveDataGenerator(def);
});
When(/^stop live data generator$/, async () => {
bSteps.stopLiveDataGenerator();
});
When(/^click the sort type dropdown$/, async () => {
await bSteps.clickSortTypeDropdown();
});
When(/^click sort by item "(.*)"$/, async item => {
await bSteps.clickSortByListItem(item);
});
Then(/^the add label popover is not present$/, async () => {
await bSteps.verifyAddLabelsPopopverNotPresent();
});
Then(/^the add label popover is present$/, async () => {
await bSteps.verifyAddLabelPopoverVisible();
});
Then(/^the add label popover contains the labels$/, async labels => {
await bSteps.verifyLabelPopoverContainsLabels(labels);
});
When(/^click the label popover item "(.*)"$/, async item => {
await bSteps.clickLabelPopoverItem(item);
});
Then(/^the add label popover does not contain the labels:$/, { timeout: 10000}, async labels => {
await bSteps.verifyLabelPopoverDoesNotContainLabels(labels);
});
When(/^set the label popover filter field to "(.*)"$/, async val => {
await bSteps.setLabelPopoverFilter(val);
});
Then(/^the label popover contains create new "(.*)"$/, async name => {
await bSteps.verifyLabelPopoverCreateNew(name);
});
Then(/^the add label popover does not contain create new$/, async () => {
await bSteps.verifyLabelPopupNoCreateNew();
});
When(/^clear the popover label selector filter$/, async () => {
await bSteps.clearDashboardLabelsFilter();
});

View File

@ -0,0 +1,586 @@
import { Then, When } from 'cucumber';
const cellOverlaySteps = require(__srcdir + '/steps/dashboards/cellOverlaySteps.js');
let celOvSteps = new cellOverlaySteps(__wdriver);
Then(/^the cell edit overlay is loaded as "(.*)"$/, {timeout: 10000}, async name => {
await celOvSteps.verifyCellOverlayIsLoaded(name);
});
When(/^get the current cell edit preview graph$/, async () => {
await celOvSteps.getCurrentCellEditPreviewGraph();
});
When(/^name dashboard cell "(.*)"$/, async name => {
await celOvSteps.nameDashboardCell(name);
});
When(/^click dashboard cell edit cancel button$/, async () => {
await celOvSteps.clickDashboardCellEditCancel();
});
When(/^click dashboard cell save button$/, async () => {
await celOvSteps.clickDashboardCellSave();
});
When(/^click the cell edit Time Range Dropdown$/, async () => {
await celOvSteps.clickCellEditTimeRangeDropdown();
});
When(/^select the cell edit Time Range "(.*)"$/, async item => {
await celOvSteps.selectCellEditTimeRangeItem(item);
});
When(/^click the cell edit Script Editor button$/, async () => {
await celOvSteps.clickCellEditScriptEditorButton();
});
When(/^paste into cell edit Script Editor$/, { timeout: 20000 }, async text => {
await celOvSteps.pasteIntoCellEditScriptEditor(text);
});
When(/^clear the cell edit Script Editor$/, { timeout: 20000 }, async () => {
await celOvSteps.clearCellEditScriptEditor();
});
Then(/^the time machine cell edit submit button is disabled$/, async () => {
await celOvSteps.verifyCellEditSubmitDisabled();
});
Then(/^the time machine cell edit submit button is enabled$/, async () => {
await celOvSteps.verifyCellEditSubmitEnabled();
});
When(/^click the time machine cell edit submit button$/, async () => {
await celOvSteps.clickCellEditSubmitButton();
});
Then(/^the time machine cell edit preview graph is shown$/, async() => {
await celOvSteps.verifyCellEditPreviewGraphVisible();
});
Then(/^the time machine cell edit preview axes are shown$/, async() => {
await celOvSteps.verifyCellEditPreviewAxesVisible();
});
Then(/^the cell edit preview graph is changed$/, async() => {
await celOvSteps.verifyCellEditPreviewGraphChanged();
});
When(/^click the cell edit save button$/, async () => {
await celOvSteps.clickCellEditSaveButton();
});
When(/^click on the cell edit name$/, async () => {
await celOvSteps.clickCellEditName();
});
When(/^change the cell edit name to "(.*)"$/, async name => {
await celOvSteps.updateCellName(name);
});
When(/^click the dashboard cell view type dropdown$/, async () => {
await celOvSteps.clickViewTypeDropdown();
});
Then(/^the dashboard cell view type dropdown list contains:$/, async itemList => {
await celOvSteps.verifyViewTypeListContents(itemList);
});
Then(/^the cell view type dropdown list is not present$/, async () => {
await celOvSteps.verifyViewTypeListNotPresent();
});
When(/^click cell view customize button$/, async () => {
await celOvSteps.clickCellViewCustomize();
});
Then(/^the view options container is present$/, async () => {
await celOvSteps.verifyViewOptionsContainerVisible();
});
Then(/^the view options container is not present$/, async () => {
await celOvSteps.verifyViewOptionsContainerNotPresent();
});
Then(/^the cell view customize button is highlighted$/, async () => {
await celOvSteps.verifyCellCustomizeButtonHighlight();
});
Then(/^the cell view customize button is not highlighted$/, async () => {
await celOvSteps.verifyCustomizeButtonNoHighlightd();
});
Then(/^the time machine view empty queries graph is visible$/, async () => {
await celOvSteps.verifyTMViewEmptyQueriesGraphVisible();
});
Then(/^the time machine view no results is visible$/, async () => {
await celOvSteps.verifyTMViewNoResultsVisible();
});
When(/^click time machine autorefresh dropdown$/, async () => {
await celOvSteps.clickTMAutorefreshDropdown();
});
Then(/^the time machine autorefresh dropdown list contains:$/, async itemList => {
await celOvSteps.verifyAutorefreshListContents(itemList);
});
When(/^select the time machine autorefresh rate "(.*)"$/, async item => {
await celOvSteps.clickTMAutorefreshItem(item);
});
Then(/^the time machine force refresh button is not present$/, async () => {
await celOvSteps.verifyTMAutorefreshForceButtonNotPresent();
});
Then(/^the time machine force refresh button is present$/, async () => {
await celOvSteps.verifyTMAutorefreshForceButtonVisible();
});
Then(/^the time machine autorefresh dropdown list is set to "(.*)"$/, async selected => {
await celOvSteps.verifyTMAutorefreshDropdownSelected(selected);
});
When(/^click time machine force refresh$/, async () => {
await celOvSteps.clickTMForceRefresh();
});
Then(/^the time machine Time Range dropdown list contains:$/, async itemList => {
await celOvSteps.verifyTMTimeRangeDropdownList(itemList);
});
Then(/^the time machine Time Range dropdown list is not present$/, async () => {
await celOvSteps.verifyTMTimeRangeDropdownListNotPresent();
});
Then(/^the time machine query builder is visible$/, async () => {
await celOvSteps.verifyTMQueryBuilderVisible();
});
Then(/^the time machine switch to Query Builder warning is present$/, async () => {
await celOvSteps.verifyTMQueryBuilderSwitchWarnVisible();
});
Then(/^the time machine switch to Query Builder warning is not present$/, async () => {
await celOvSteps.verifyTMQueryBuilderSwitchWarnNotPresent();
});
Then(/^the time machine flux editor is visible$/, async () => {
await celOvSteps.verifyTMFluxEditorVisible();
});
When(/^click the cell edit Query Builder button$/, async () => {
await celOvSteps.clickTMSwitch2QBuilder();
});
When(/^click the cell edit Query Builder confirm button$/, async () => {
await celOvSteps.clickTMSwitch2QBuilderConfirm();
});
When(/^click the time machine flux editor$/, async () => {
await celOvSteps.clickTMFluxEditor();
});
When(/^click the filter functions input$/, async () => {
await celOvSteps.clickTMFilterFunctionsInput();
});
Then(/^the time machine flux editor is not present$/, async () => {
await celOvSteps.verifyTMFluxEditorNotPresent();
});
Then(/^the edit cell bucket selector contains buckets:$/, async bucketList => {
await celOvSteps.verifyTMBucketListContents(bucketList);
});
When(/^click the time machine bucket selector item "(.*)"$/, async item => {
await celOvSteps.driver.sleep(500); //troubleshoot issue with click on wrong item
await celOvSteps.clickTMBucketSelectorItem(item);
});
Then(/^the bucket "(.*)" is not present in the time machine bucket selector$/, async bucket => {
await celOvSteps.verifyBucketNotInTMBucketList(bucket);
});
When(/^filter the time machine bucket selector with "(.*)"$/, async value => {
await celOvSteps.filterBucketListContents(value);
});
When(/^clear the time machine bucket selector filter$/, async () => {
await celOvSteps.clearBucketSelectorFilter();
});
Then(/^there are "(.*)" time machine builder cards$/, async count => {
await celOvSteps.verifyTMBuilderCardsSize(count);
});
Then(/^time machine builder card "(.*)" contains:$/, async (index,items) => {
await celOvSteps.verifyItemsInBuilderCard(index,items);
});
Then(/^the item "(.*)" in builder card "(.*)" is selected$/, async (item,index) => {
await celOvSteps.verifyItemSelectedInBuilderCard(index, item);
})
Then(/^time machine bulider card "(.*)" contains the empty tag message$/, async index => {
await celOvSteps.verifyEmptyTagsInBuilderCard(index);
});
Then(/^there are no selected tags in time machine builder card "(.*)"$/, async index => {
await celOvSteps.verifyNoSelectedTagsInBuilderCard(index);
});
Then(/^the selector count for builder card "(.*)" contains the value "(.*)"$/, async (index,value) =>{
await celOvSteps.verifySelectorCountInBuilderCard(index,value);
});
Then(/^time machine builder card "(.*)" does not contain "(.*)"$/, async (index, item) => {
await celOvSteps.verifyItemNotInBuilderCard(index,item);
});
When(/^click the tag selector dropdown of builder card "(.*)"$/, async index => {
await celOvSteps.clickTagSelectorOfBuilderCard(index);
});
Then(/^the tag selector dropdown of builder card "(.*)" contains:$/, async (index,items) => {
await celOvSteps.verifyItemsInBuilderCardTagSelector(index,items);
});
When(/^click the tag selector dropdown item "(.*)" of builder card "(.*)"$/, async (item, index) => {
await celOvSteps.clickTagSelectorDropdownItemInBuilderCard(item,index);
});
When(/^click the tag "(.*)" in builder card "(.*)"$/, async (tag, cardIndex) => {
await celOvSteps.clickTagInBuilderCard(tag, cardIndex);
});
When(/^filter the tags in time machine builder card "(.*)" with "(.*)"$/, {timeout: 10000}, async (index,term) => {
await celOvSteps.filterBuilderCardListContents(index,term);
});
Then(/^time machine builder card "(.*)" is empty$/, async index => {
await celOvSteps.verifyBuilderCardEmpty(index);
});
When(/^clear the tags filter in time machine builder card "(.*)"$/, async index => {
await celOvSteps.clearTagsFilterInBuilderCard(index);
});
Then(/^the contents of tag selector dropodwn of build card "(.*)" are not present$/, async index => {
await celOvSteps.verifyBuilderCardTagSelectNotPresent(index);
});
Then(/^the selector counf for builder card "(.*)" is not present$/, async index => {
await celOvSteps.verifyBuilderCardSelectCountNotPresent(index);
});
Then(/^the delete button for builder card "(.*)" is not present$/, async index => {
await celOvSteps.verifyBuilderCardDeleteNotPresent(index);
});
When(/^click delete for builder card "(.*)"$/, async index => {
await celOvSteps.clickBuilderCardDelete(index);
});
Then(/^the time machine query builder function duration period is "(.*)"$/, async duration => {
await celOvSteps.verifyTMQueryBuilderFunctionDuration(duration);
});
When(/^click the time machine query builder function duration input$/, async () => {
await celOvSteps.clickTMQueryBuilderFunctionDuration();
});
Then(/^the query builder function duration suggestion drop down contains "(.*)" suggestions$/, async count => {
await celOvSteps.verifyTMQBFunctionDurationSuggestionCount(count);
});
Then(/^the query builder function duration suggestion drop down includes$/, async items => {
await celOvSteps.verifyTMQBFunctionDurationSuggestionItems(items);
});
When(/^click the query builder function duration suggestion "(.*)"$/, async item => {
await celOvSteps.clickTMQBFunctionDurationSuggestionItem(item);
});
Then(/^the query builder function list contains$/, async items => {
await celOvSteps.verifyTMQueryBuilderFunctionListItems(items);
});
Then(/^the query builder function list has "(.*)" items$/, async count => {
await celOvSteps.verifyQuerBuilderFunctionListItemCount(count);
});
When(/^filter the query builder function list with "(.*)"$/, async term => {
await celOvSteps.filterQueryBuilderFunctionList(term);
});
When(/^clear the query builder function lis filter$/, async () => {
await celOvSteps.clearQueryBuilderFunctionListFilter();
});
When(/^get metrics of time machine cell edit preview$/, async () => {
await celOvSteps.getTMPreviewMetrics();
});
When(/^get metrics of time machine query builder$/, async () => {
await celOvSteps.getTMPQueryAreaMetrics();
});
When(/^get time machine preview canvas$/, async () => {
await celOvSteps.getTMPreviewCanvas();
});
When(/^get time machine preview axes$/, async () => {
await celOvSteps.getTMPreviewCanvasAxes();
});
When(/^resize time machine preview area by "(.*)"$/, async dims => {
let deltaSize = JSON.parse(dims);
await celOvSteps.resizeTMPreviewBy(deltaSize);
});
Then(/^the time machine preview area has changed by "(.*)"$/, async dims => {
let deltaSize = JSON.parse(dims);
await celOvSteps.verifyTMPreviewAreaSizeChange(deltaSize);
});
Then(/^the time machine query builder area has changed by "(.*)"$/, async dims => {
let deltaSize = JSON.parse(dims);
await celOvSteps.verifyTMQBAreaSizeChange(deltaSize);
});
Then(/^the time machine preview canvas has changed$/, {timeout: 10000}, async () => {
await celOvSteps.verifyTMPreviewCanvasChange();
});
Then(/^the time machine preview canvas has not changed$/, {timeout: 1000}, async () => {
await celOvSteps.verifyTMPreviewCanvasNoChange();
});
Then(/^the time machine preview axes have changed$/, async () => {
await celOvSteps.verifyTMPreviewAxesChange();
});
Then(/^the time machine preview canvas is not present$/, async () => {
await celOvSteps.verifyTMPreviewCanvasNotPresent();
});
Then(/^the time machine preview canvas axes are not present$/, async () => {
await celOvSteps.verifyTMPreviewCanvasAxesNotPresent();
});
When(/^click the time machine query builder add query button$/, async () => {
await celOvSteps.clickTMAddQuery();
});
Then(/^the bucket selected in the current time machine query is "(.*)"$/, async bucket => {
await celOvSteps.verifyTMQueryBucketSelected(bucket);
});
Then(/^the tag selected in the current time machine query card "(.*)" is "(.*)"$/, async (index, tag) => {
await celOvSteps.verifyTMQueryCardSelected(index,tag);
});
Then(/^the functions selected in the current time machine query card are "(.*)"$/, async funcs => {
await celOvSteps.verifyTMQueryFunctionsSelected(funcs);
});
When(/^click the query builder function "(.*)"$/, async func => {
await celOvSteps.clickTMQBFunction(func);
});
Then(/^query "(.*)" is the active query in query builder$/, async title => {
await celOvSteps.verifyTMQBActiveQuery(title);
});
When(/^click on query "(.*)" in the query builder$/, async title => {
await celOvSteps.clickOnTMQBQueryTab(title);
});
When(/^right click on the time machine query tab title "(.*)"$/, async title => {
await celOvSteps.rightClickTMQBQueryTabTitle(title);
});
When(/^click the time machine query tab right click menu item "(.*)"$/, async item => {
await celOvSteps.clickTMQBQueryTabRightClickMenuItem(item);
});
When(/^enter "(.*)" into the time machine query tab name input$/, async name => {
await celOvSteps.enterNewTMQBQueryTabName(name);
});
Then(/^there is no time machine query tab named "(.*)"$/, async name => {
await celOvSteps.verifyNoTMQBQueryTabNamed(name);
});
When(/^click hide query of time machine query tab "(.*)"$/, async name => {
await celOvSteps.clickTMQBHideQuery(name);
});
When(/^right click the time machine query tab "(.*)"$/, async name => {
await celOvSteps.clickRightTMQBQuery(name);
});
When(/^click delete of time machine query tab "(.*)"$/, async name => {
await celOvSteps.clickTMQBDeleteQuery(name);
});
Then(/^there are "(.*)" time machine query tabs$/, async count => {
await celOvSteps.verifyTMQBNumberOfQueryTabs(count);
});
Then(/^the time machine script editor contains$/, async script => {
await celOvSteps.verifyTMQBScriptEditorContents(script);
});
When(/^change the time machine script editor contents to:$/, { timeout: 60000 }, async script => {
await celOvSteps.updateTMQBScriptEditorContents(script);
});
When(/^set the time machine script editor contents to:$/, {timeout: 60000}, async script => {
await celOvSteps.updateTMQBScriptEditorContents(script);
});
When(/^click the time machine switch to query builder button$/, async () => {
await celOvSteps.clickTMSwitch2QBuilder();
});
Then(/^the time machine empty graph error message is:$/, async msg => {
await celOvSteps.verifyTMEmptyGraphErrorMessage(msg);
});
When(/^close all time machine builder cards$/, async () => {
await celOvSteps.closeAllTMQBCards();
});
When(/^unselect any tags in time machine builder card "(.*)"$/, async index => {
await celOvSteps.deselectAllActiveTagsInTMQBCard(index);
});
Then(/^the time machine query edit function categories are displayed:$/, async cats => {
await celOvSteps.verifyTMQEFunctionCategoriesDisplayed(cats);
});
When(/^filter the time machine query edit function list with "(.*)"$/, async term => {
await celOvSteps.filterTMQEFunctionsList(term);
});
When(/^clear the time machine query edit function list filter$/, async () => {
await celOvSteps.clearTMQEFunctionsListFilter();
});
Then(/^the following function are visible in the time machine function list:$/, async funcs => {
await celOvSteps.verifyTMQEVisibleFunctions(funcs);
});
Then(/^the following function are not visible in the time machine function list:$/, {timeout: 20000}, async funcs => {
await celOvSteps.verifyTMQENotVisibleFunctions(funcs);
});
When(/^click the time machine query editor function "(.*)"$/, async func => {
await celOvSteps.clickTMQEFunction(func);
});
When(/^click inject the time machine query editor function "(.*)"$/, async func => {
await celOvSteps.clickInjectTMQEFunction(func);
});
When(/^hover over time machine query edit function "(.*)"$/, async func => {
await celOvSteps.hoverOverTMQEFunction(func);
});
Then(/^the time machine query edit function popup description contains:$/, async text => {
await celOvSteps.verifyTMQEFunctionPopupDescription(text);
});
Then(/^the time machine query edit function popup snippet contains:$/, async text => {
await celOvSteps.verifyTMQEFunctionPopupSnippet(text);
});
Then(/^the time machine query edit function popup is not visible$/, async () => {
await celOvSteps.verifyTMQEFunctionPopupNotVisible();
});
When(/^hover over the time machine query editor timerange dropdown button$/, async () => {
await celOvSteps.hoverOverTMQETimerangeDropdown();
});
When(/^hover over the time machine query editor submit button$/, async() => {
await celOvSteps.hoverOverTMCellEditSubmit();
});
When(/^send keys "(.*)" to the time machine flux editor$/, async keys => {
await celOvSteps.sendKeysToTimeMachineFluxEditor(keys);
});
Then(/^the time machine raw data table is not present$/, async () => {
await celOvSteps.verifyTMRawDataTableNotPresent();
});
Then(/^the time machine raw data table is present$/, async () => {
await celOvSteps.verifyTMRawDataTablePresent();
});
When(/^click time machine raw data toggle$/, async () => {
await celOvSteps.clickTMRawDataToggle();
});
Then(/^time machine raw data cell "(.*)" contains "(.*)"$/, async (coords, value) => {
let cartesCoords = JSON.parse(coords);
await celOvSteps.verifyTMRawDataCellContents(cartesCoords, value);
});
When(/^scroll time machine raw data "(.*)"$/, async (d_coords) => {
let cartesCoords = JSON.parse(d_coords);
await celOvSteps.scrollTMRawDataTable(cartesCoords);
});
When(/^click time machine download CSV$/, async () => {
await celOvSteps.clickTMDownloadCSV();
});
When(/^click the time machine script editor variables tab$/, async () => {
await celOvSteps.clickTMQEVariablesTab();
});
Then(/^the time machine variables list contains$/, async varList => {
await celOvSteps.verifyTMQEVariablesList(varList);
});
Then(/^the time machine variables list does not contain$/, {timeout: 10000}, async varList => {
await celOvSteps.verifyTMQWVarieblesListAbsent(varList);
});
When(/^enter the value "(.*)" in the time machine variables filter$/, async value => {
await celOvSteps.enterTMQEVariableFilterValue(value);
});
When(/^clear the time machine variables filter$/, async () => {
await celOvSteps.clearTMQEVariablesFilter();
});
When(/^hover over the time machine variable "(.*)"$/, async varname => {
await celOvSteps.hoverOverTMQEVariable(varname);
});
When(/^click the time machine variable "(.*)"$/, async varname => {
await celOvSteps.clickTMQEVariable(varname);
});
When(/^click inject the time machine variable "(.*)"$/, async varname => {
await celOvSteps.clickInjectTMQEVariable(varname);
});
Then(/^the time machine variable popover is not visible$/, async () => {
await celOvSteps.verifyTMQEVariablePopoverNotVisible();
});
Then(/^the time machine variable popover is visible$/, async () => {
await celOvSteps.verifyTMQEVariablePopoverVisible();
});
When(/^click time machine popover variable dropodown$/, async () => {
await celOvSteps.clickTMQEVariablePopoverVarDropdown();
});

Some files were not shown because too many files have changed in this diff Show More