Merge branch 'master' into feat/use-algo-w
commit
0c8d19e896
|
@ -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
12
auth.go
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
15
authz.go
15
authz.go
|
@ -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"`
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
139
dbrp_mapping.go
139
dbrp_mapping.go
|
@ -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.
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"presets": [
|
||||
[
|
||||
"env",
|
||||
{
|
||||
"targets": {
|
||||
"node": "current"
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
|
@ -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
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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.
|
|
@ -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'
|
||||
};
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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": []
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
{}
|
|
@ -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": []
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
bad data
|
|
@ -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": []
|
||||
}
|
|
@ -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": []
|
||||
}
|
|
@ -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": []
|
||||
}
|
|
@ -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": []
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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}
|
||||
]
|
||||
}
|
|
@ -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": []
|
||||
}
|
|
@ -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": []
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
{}
|
|
@ -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": []
|
||||
}
|
|
@ -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": []
|
||||
}
|
|
@ -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": []
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
@ -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|
|
|
@ -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"
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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 |
|
|
@ -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
|
|
@ -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 |
|
||||
|
||||
|
||||
|
|
@ -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.
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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 |
|
|
@ -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
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
FROM quay.io/influxdb/influx:nightly
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh", "--e2e-testing=true"]
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
||||
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
||||
|
|
@ -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
Loading…
Reference in New Issue