Merge branch 'master' into flux-staging

pull/10956/head
Nathaniel Cook 2019-01-15 08:35:59 -07:00
commit 8372859dee
59 changed files with 2730 additions and 731 deletions

View File

@ -57,31 +57,31 @@ func init() {
RunE: authorizationCreateF,
}
authorizationCreateCmd.Flags().StringVarP(&authorizationCreateFlags.org, "org", "o", "", "org name (required)")
authorizationCreateCmd.Flags().StringVarP(&authorizationCreateFlags.org, "org", "o", "", "The organization name (required)")
authorizationCreateCmd.MarkFlagRequired("org")
authorizationCreateCmd.Flags().StringVarP(&authorizationCreateFlags.user, "user", "u", "", "user name")
authorizationCreateCmd.Flags().StringVarP(&authorizationCreateFlags.user, "user", "u", "", "The user name")
authorizationCreateCmd.Flags().BoolVarP(&authorizationCreateFlags.writeUserPermission, "write-user", "", false, "grants the permission to perform mutative actions against organization users")
authorizationCreateCmd.Flags().BoolVarP(&authorizationCreateFlags.readUserPermission, "read-user", "", false, "grants the permission to perform read actions against organization users")
authorizationCreateCmd.Flags().BoolVarP(&authorizationCreateFlags.writeUserPermission, "write-user", "", false, "Grants the permission to perform mutative actions against organization users")
authorizationCreateCmd.Flags().BoolVarP(&authorizationCreateFlags.readUserPermission, "read-user", "", false, "Grants the permission to perform read actions against organization users")
authorizationCreateCmd.Flags().BoolVarP(&authorizationCreateFlags.writeBucketsPermission, "write-buckets", "", false, "grants the permission to perform mutative actions against organization buckets")
authorizationCreateCmd.Flags().BoolVarP(&authorizationCreateFlags.readBucketsPermission, "read-buckets", "", false, "grants the permission to perform read actions against organization buckets")
authorizationCreateCmd.Flags().BoolVarP(&authorizationCreateFlags.writeBucketsPermission, "write-buckets", "", false, "Grants the permission to perform mutative actions against organization buckets")
authorizationCreateCmd.Flags().BoolVarP(&authorizationCreateFlags.readBucketsPermission, "read-buckets", "", false, "Grants the permission to perform read actions against organization buckets")
authorizationCreateCmd.Flags().StringArrayVarP(&authorizationCreateFlags.writeBucketPermissions, "write-bucket", "", []string{}, "bucket id")
authorizationCreateCmd.Flags().StringArrayVarP(&authorizationCreateFlags.readBucketPermissions, "read-bucket", "", []string{}, "bucket id")
authorizationCreateCmd.Flags().StringArrayVarP(&authorizationCreateFlags.writeBucketPermissions, "write-bucket", "", []string{}, "The bucket id")
authorizationCreateCmd.Flags().StringArrayVarP(&authorizationCreateFlags.readBucketPermissions, "read-bucket", "", []string{}, "The bucket id")
authorizationCreateCmd.Flags().BoolVarP(&authorizationCreateFlags.writeTasksPermission, "write-tasks", "", false, "grants the permission to create tasks")
authorizationCreateCmd.Flags().BoolVarP(&authorizationCreateFlags.readTasksPermission, "read-tasks", "", false, "grants the permission to read tasks")
authorizationCreateCmd.Flags().BoolVarP(&authorizationCreateFlags.writeTasksPermission, "write-tasks", "", false, "Grants the permission to create tasks")
authorizationCreateCmd.Flags().BoolVarP(&authorizationCreateFlags.readTasksPermission, "read-tasks", "", false, "Grants the permission to read tasks")
authorizationCreateCmd.Flags().BoolVarP(&authorizationCreateFlags.writeTelegrafsPermission, "write-telegrafs", "", false, "grants the permission to create telegraf configs")
authorizationCreateCmd.Flags().BoolVarP(&authorizationCreateFlags.readTelegrafsPermission, "read-telegrafs", "", false, "grants the permission to read telegraf configs")
authorizationCreateCmd.Flags().BoolVarP(&authorizationCreateFlags.writeTelegrafsPermission, "write-telegrafs", "", false, "Grants the permission to create telegraf configs")
authorizationCreateCmd.Flags().BoolVarP(&authorizationCreateFlags.readTelegrafsPermission, "read-telegrafs", "", false, "Grants the permission to read telegraf configs")
authorizationCreateCmd.Flags().BoolVarP(&authorizationCreateFlags.writeOrganizationsPermission, "write-orgs", "", false, "grants the permission to create organizations")
authorizationCreateCmd.Flags().BoolVarP(&authorizationCreateFlags.readOrganizationsPermission, "read-orgs", "", false, "grants the permission to read organizations")
authorizationCreateCmd.Flags().BoolVarP(&authorizationCreateFlags.writeOrganizationsPermission, "write-orgs", "", false, "Grants the permission to create organizations")
authorizationCreateCmd.Flags().BoolVarP(&authorizationCreateFlags.readOrganizationsPermission, "read-orgs", "", false, "Grants the permission to read organizations")
authorizationCreateCmd.Flags().BoolVarP(&authorizationCreateFlags.writeDashboardsPermission, "write-dashboards", "", false, "grants the permission to create dashboards")
authorizationCreateCmd.Flags().BoolVarP(&authorizationCreateFlags.readDashboardsPermission, "read-dashboards", "", false, "grants the permission to read dashboards")
authorizationCreateCmd.Flags().BoolVarP(&authorizationCreateFlags.writeDashboardsPermission, "write-dashboards", "", false, "Grants the permission to create dashboards")
authorizationCreateCmd.Flags().BoolVarP(&authorizationCreateFlags.readDashboardsPermission, "read-dashboards", "", false, "Grants the permission to read dashboards")
authorizationCmd.AddCommand(authorizationCreateCmd)
}
@ -297,9 +297,9 @@ func init() {
RunE: authorizationFindF,
}
authorizationFindCmd.Flags().StringVarP(&authorizationFindFlags.user, "user", "u", "", "user")
authorizationFindCmd.Flags().StringVarP(&authorizationFindFlags.userID, "user-id", "", "", "user ID")
authorizationFindCmd.Flags().StringVarP(&authorizationFindFlags.id, "id", "i", "", "authorization ID")
authorizationFindCmd.Flags().StringVarP(&authorizationFindFlags.user, "user", "u", "", "The user")
authorizationFindCmd.Flags().StringVarP(&authorizationFindFlags.userID, "user-id", "", "", "The user ID")
authorizationFindCmd.Flags().StringVarP(&authorizationFindFlags.id, "id", "i", "", "The authorization ID")
authorizationCmd.AddCommand(authorizationFindCmd)
}
@ -398,7 +398,7 @@ func init() {
RunE: authorizationDeleteF,
}
authorizationDeleteCmd.Flags().StringVarP(&authorizationDeleteFlags.id, "id", "i", "", "authorization id (required)")
authorizationDeleteCmd.Flags().StringVarP(&authorizationDeleteFlags.id, "id", "i", "", "The authorization ID (required)")
authorizationDeleteCmd.MarkFlagRequired("id")
authorizationCmd.AddCommand(authorizationDeleteCmd)
@ -463,11 +463,11 @@ var authorizationActiveFlags AuthorizationActiveFlags
func init() {
authorizationActiveCmd := &cobra.Command{
Use: "active",
Short: "active authorization",
Short: "Active authorization",
RunE: authorizationActiveF,
}
authorizationActiveCmd.Flags().StringVarP(&authorizationActiveFlags.id, "id", "i", "", "authorization id (required)")
authorizationActiveCmd.Flags().StringVarP(&authorizationActiveFlags.id, "id", "i", "", "The authorization ID (required)")
authorizationActiveCmd.MarkFlagRequired("id")
authorizationCmd.AddCommand(authorizationActiveCmd)
@ -532,11 +532,11 @@ var authorizationInactiveFlags AuthorizationInactiveFlags
func init() {
authorizationInactiveCmd := &cobra.Command{
Use: "inactive",
Short: "inactive authorization",
Short: "Inactive authorization",
RunE: authorizationInactiveF,
}
authorizationInactiveCmd.Flags().StringVarP(&authorizationInactiveFlags.id, "id", "i", "", "authorization id (required)")
authorizationInactiveCmd.Flags().StringVarP(&authorizationInactiveFlags.id, "id", "i", "", "The authorization ID (required)")
authorizationInactiveCmd.MarkFlagRequired("id")
authorizationCmd.AddCommand(authorizationInactiveCmd)

View File

@ -17,7 +17,7 @@ import (
// Bucket Command
var bucketCmd = &cobra.Command{
Use: "bucket",
Short: "bucket related commands",
Short: "Bucket management commands",
Run: bucketF,
}
@ -42,10 +42,10 @@ func init() {
Run: bucketCreateF,
}
bucketCreateCmd.Flags().StringVarP(&bucketCreateFlags.name, "name", "n", "", "name of bucket that will be created")
bucketCreateCmd.Flags().DurationVarP(&bucketCreateFlags.retention, "retention", "r", 0, "duration in nanoseconds data will live in bucket")
bucketCreateCmd.Flags().StringVarP(&bucketCreateFlags.org, "org", "o", "", "name of the organization that owns the bucket")
bucketCreateCmd.Flags().StringVarP(&bucketCreateFlags.orgID, "org-id", "", "", "id of the organization that owns the bucket")
bucketCreateCmd.Flags().StringVarP(&bucketCreateFlags.name, "name", "n", "", "Name of bucket that will be created")
bucketCreateCmd.Flags().DurationVarP(&bucketCreateFlags.retention, "retention", "r", 0, "Duration in nanoseconds data will live in bucket")
bucketCreateCmd.Flags().StringVarP(&bucketCreateFlags.org, "org", "o", "", "Name of the organization that owns the bucket")
bucketCreateCmd.Flags().StringVarP(&bucketCreateFlags.orgID, "org-id", "", "", "The ID of the organization that owns the bucket")
bucketCreateCmd.MarkFlagRequired("name")
bucketCmd.AddCommand(bucketCreateCmd)
@ -143,10 +143,10 @@ func init() {
Run: bucketFindF,
}
bucketFindCmd.Flags().StringVarP(&bucketFindFlags.name, "name", "n", "", "bucket name")
bucketFindCmd.Flags().StringVarP(&bucketFindFlags.id, "id", "i", "", "bucket ID")
bucketFindCmd.Flags().StringVarP(&bucketFindFlags.orgID, "org-id", "", "", "bucket organization ID")
bucketFindCmd.Flags().StringVarP(&bucketFindFlags.org, "org", "o", "", "bucket organization name")
bucketFindCmd.Flags().StringVarP(&bucketFindFlags.name, "name", "n", "", "The bucket name")
bucketFindCmd.Flags().StringVarP(&bucketFindFlags.id, "id", "i", "", "The bucket ID")
bucketFindCmd.Flags().StringVarP(&bucketFindFlags.orgID, "org-id", "", "", "The bucket organization ID")
bucketFindCmd.Flags().StringVarP(&bucketFindFlags.org, "org", "o", "", "The bucket organization name")
bucketCmd.AddCommand(bucketFindCmd)
}
@ -233,9 +233,9 @@ func init() {
Run: bucketUpdateF,
}
bucketUpdateCmd.Flags().StringVarP(&bucketUpdateFlags.id, "id", "i", "", "bucket ID (required)")
bucketUpdateCmd.Flags().StringVarP(&bucketUpdateFlags.name, "name", "n", "", "new bucket name")
bucketUpdateCmd.Flags().DurationVarP(&bucketUpdateFlags.retention, "retention", "r", 0, "new duration data will live in bucket")
bucketUpdateCmd.Flags().StringVarP(&bucketUpdateFlags.id, "id", "i", "", "The bucket ID (required)")
bucketUpdateCmd.Flags().StringVarP(&bucketUpdateFlags.name, "name", "n", "", "New bucket name")
bucketUpdateCmd.Flags().DurationVarP(&bucketUpdateFlags.retention, "retention", "r", 0, "New duration data will live in bucket")
bucketUpdateCmd.MarkFlagRequired("id")
bucketCmd.AddCommand(bucketUpdateCmd)
@ -345,7 +345,7 @@ func init() {
Run: bucketDeleteF,
}
bucketDeleteCmd.Flags().StringVarP(&bucketDeleteFlags.id, "id", "i", "", "bucket id (required)")
bucketDeleteCmd.Flags().StringVarP(&bucketDeleteFlags.id, "id", "i", "", "The bucket ID (required)")
bucketDeleteCmd.MarkFlagRequired("id")
bucketCmd.AddCommand(bucketDeleteCmd)

View File

@ -80,12 +80,25 @@ func init() {
}
influxCmd.PersistentFlags().BoolVar(&flags.local, "local", false, "Run commands locally against the filesystem")
// Override help on all the commands tree
walk(influxCmd, func(c *cobra.Command) {
c.Flags().BoolP("help", "h", false, fmt.Sprintf("Help for the %s command ", c.Name()))
})
}
func influxF(cmd *cobra.Command, args []string) {
cmd.Usage()
}
// walk calls f for c and all of its children.
func walk(c *cobra.Command, f func(*cobra.Command)) {
f(c)
for _, c := range c.Commands() {
walk(c, f)
}
}
// Execute executes the influx command
func Execute() {
if err := influxCmd.Execute(); err != nil {

View File

@ -17,7 +17,7 @@ import (
var organizationCmd = &cobra.Command{
Use: "org",
Aliases: []string{"organization"},
Short: "Organization related commands",
Short: "Organization management commands",
Run: organizationF,
}
@ -39,7 +39,7 @@ func init() {
Run: organizationCreateF,
}
organizationCreateCmd.Flags().StringVarP(&organizationCreateFlags.name, "name", "n", "", "name of organization that will be created")
organizationCreateCmd.Flags().StringVarP(&organizationCreateFlags.name, "name", "n", "", "The name of organization that will be created")
organizationCreateCmd.MarkFlagRequired("name")
organizationCmd.AddCommand(organizationCreateCmd)
@ -109,8 +109,8 @@ func init() {
Run: organizationFindF,
}
organizationFindCmd.Flags().StringVarP(&organizationFindFlags.name, "name", "n", "", "organization name")
organizationFindCmd.Flags().StringVarP(&organizationFindFlags.id, "id", "i", "", "organization id")
organizationFindCmd.Flags().StringVarP(&organizationFindFlags.name, "name", "n", "", "The organization name")
organizationFindCmd.Flags().StringVarP(&organizationFindFlags.id, "id", "i", "", "The organization ID")
organizationCmd.AddCommand(organizationFindCmd)
}
@ -171,8 +171,8 @@ func init() {
Run: organizationUpdateF,
}
organizationUpdateCmd.Flags().StringVarP(&organizationUpdateFlags.id, "id", "i", "", "organization ID (required)")
organizationUpdateCmd.Flags().StringVarP(&organizationUpdateFlags.name, "name", "n", "", "organization name")
organizationUpdateCmd.Flags().StringVarP(&organizationUpdateFlags.id, "id", "i", "", "The organization ID (required)")
organizationUpdateCmd.Flags().StringVarP(&organizationUpdateFlags.name, "name", "n", "", "The organization name")
organizationUpdateCmd.MarkFlagRequired("id")
organizationCmd.AddCommand(organizationUpdateCmd)
@ -214,7 +214,7 @@ func organizationUpdateF(cmd *cobra.Command, args []string) {
w.Flush()
}
// Delete command
// OrganizationDeleteFlags contains the flag of the org delete command
type OrganizationDeleteFlags struct {
id string
}
@ -267,7 +267,7 @@ func init() {
Run: organizationDeleteF,
}
organizationDeleteCmd.Flags().StringVarP(&organizationDeleteFlags.id, "id", "i", "", "organization id (required)")
organizationDeleteCmd.Flags().StringVarP(&organizationDeleteFlags.id, "id", "i", "", "The organization ID (required)")
organizationDeleteCmd.MarkFlagRequired("id")
organizationCmd.AddCommand(organizationDeleteCmd)
@ -276,7 +276,7 @@ func init() {
// Member management
var organizationMembersCmd = &cobra.Command{
Use: "members",
Short: "organization membership commands",
Short: "Organization membership commands",
Run: organizationF,
}
@ -363,8 +363,8 @@ func init() {
Run: organizationMembersListF,
}
organizationMembersListCmd.Flags().StringVarP(&organizationMembersListFlags.id, "id", "i", "", "organization id")
organizationMembersListCmd.Flags().StringVarP(&organizationMembersListFlags.name, "name", "n", "", "organization name")
organizationMembersListCmd.Flags().StringVarP(&organizationMembersListFlags.id, "id", "i", "", "The organization ID")
organizationMembersListCmd.Flags().StringVarP(&organizationMembersListFlags.name, "name", "n", "", "The organization name")
organizationMembersCmd.AddCommand(organizationMembersListCmd)
}
@ -450,9 +450,9 @@ func init() {
Run: organizationMembersAddF,
}
organizationMembersAddCmd.Flags().StringVarP(&organizationMembersAddFlags.id, "id", "i", "", "organization id")
organizationMembersAddCmd.Flags().StringVarP(&organizationMembersAddFlags.name, "name", "n", "", "organization name")
organizationMembersAddCmd.Flags().StringVarP(&organizationMembersAddFlags.memberId, "member", "o", "", "member id")
organizationMembersAddCmd.Flags().StringVarP(&organizationMembersAddFlags.id, "id", "i", "", "The organization ID")
organizationMembersAddCmd.Flags().StringVarP(&organizationMembersAddFlags.name, "name", "n", "", "The organization name")
organizationMembersAddCmd.Flags().StringVarP(&organizationMembersAddFlags.memberId, "member", "o", "", "The member ID")
organizationMembersAddCmd.MarkFlagRequired("member")
organizationMembersCmd.AddCommand(organizationMembersAddCmd)
@ -533,9 +533,9 @@ func init() {
Run: organizationMembersRemoveF,
}
organizationMembersRemoveCmd.Flags().StringVarP(&organizationMembersRemoveFlags.id, "id", "i", "", "organization id")
organizationMembersRemoveCmd.Flags().StringVarP(&organizationMembersRemoveFlags.name, "name", "n", "", "organization name")
organizationMembersRemoveCmd.Flags().StringVarP(&organizationMembersRemoveFlags.memberId, "member", "o", "", "member id")
organizationMembersRemoveCmd.Flags().StringVarP(&organizationMembersRemoveFlags.id, "id", "i", "", "The organization ID")
organizationMembersRemoveCmd.Flags().StringVarP(&organizationMembersRemoveFlags.name, "name", "n", "", "The organization name")
organizationMembersRemoveCmd.Flags().StringVarP(&organizationMembersRemoveFlags.memberId, "member", "o", "", "The member ID")
organizationMembersRemoveCmd.MarkFlagRequired("member")
organizationMembersCmd.AddCommand(organizationMembersRemoveCmd)

View File

@ -13,9 +13,9 @@ import (
var queryCmd = &cobra.Command{
Use: "query [query literal or @/path/to/query.flux]",
Short: "Execute an Flux query",
Short: "Execute a Flux query",
Long: `Execute a literal Flux query provided as a string,
or execute a literal Flux query contained in a file by specifying the file prefixed with an @ sign.`,
or execute a literal Flux query contained in a file by specifying the file prefixed with an @ sign.`,
Args: cobra.ExactArgs(1),
Run: fluxQueryF,
}
@ -25,7 +25,7 @@ var queryFlags struct {
}
func init() {
queryCmd.PersistentFlags().StringVar(&queryFlags.OrgID, "org-id", "", "Organization ID")
queryCmd.PersistentFlags().StringVar(&queryFlags.OrgID, "org-id", "", "The organization ID")
viper.BindEnv("ORG_ID")
if h := viper.GetString("ORG_ID"); h != "" {
queryFlags.OrgID = h

View File

@ -27,13 +27,13 @@ var replFlags struct {
}
func init() {
replCmd.PersistentFlags().StringVar(&replFlags.OrgID, "org-id", "", "ID of organization to query")
replCmd.PersistentFlags().StringVar(&replFlags.OrgID, "org-id", "", "The ID of organization to query")
viper.BindEnv("ORG_ID")
if h := viper.GetString("ORG_ID"); h != "" {
replFlags.OrgID = h
}
replCmd.PersistentFlags().StringVarP(&replFlags.Org, "org", "o", "", "name of the organization")
replCmd.PersistentFlags().StringVarP(&replFlags.Org, "org", "o", "", "The name of the organization")
viper.BindEnv("ORG")
if h := viper.GetString("ORG"); h != "" {
replFlags.Org = h

View File

@ -15,7 +15,7 @@ import (
// task Command
var taskCmd = &cobra.Command{
Use: "task",
Short: "task related commands",
Short: "Task management commands",
Run: taskF,
}
@ -30,7 +30,7 @@ func taskF(cmd *cobra.Command, args []string) {
var logCmd = &cobra.Command{
Use: "log",
Short: "log related commands",
Short: "Log related commands",
Run: logF,
}
@ -40,7 +40,7 @@ func logF(cmd *cobra.Command, args []string) {
var runCmd = &cobra.Command{
Use: "run",
Short: "run related commands",
Short: "Run related commands",
Run: runF,
}

View File

@ -36,8 +36,8 @@ func init() {
Run: userUpdateF,
}
userUpdateCmd.Flags().StringVarP(&userUpdateFlags.id, "id", "i", "", "user id (required)")
userUpdateCmd.Flags().StringVarP(&userUpdateFlags.name, "name", "n", "", "user name")
userUpdateCmd.Flags().StringVarP(&userUpdateFlags.id, "id", "i", "", "The user ID (required)")
userUpdateCmd.Flags().StringVarP(&userUpdateFlags.name, "name", "n", "", "The user name")
userUpdateCmd.MarkFlagRequired("id")
userCmd.AddCommand(userUpdateCmd)
@ -133,7 +133,7 @@ func init() {
Run: userCreateF,
}
userCreateCmd.Flags().StringVarP(&userCreateFlags.name, "name", "n", "", "user name (required)")
userCreateCmd.Flags().StringVarP(&userCreateFlags.name, "name", "n", "", "The user name (required)")
userCreateCmd.MarkFlagRequired("name")
userCmd.AddCommand(userCreateCmd)
@ -182,8 +182,8 @@ func init() {
Run: userFindF,
}
userFindCmd.Flags().StringVarP(&userFindFlags.id, "id", "i", "", "user ID")
userFindCmd.Flags().StringVarP(&userFindFlags.name, "name", "n", "", "user name")
userFindCmd.Flags().StringVarP(&userFindFlags.id, "id", "i", "", "The user ID")
userFindCmd.Flags().StringVarP(&userFindFlags.name, "name", "n", "", "The user name")
userCmd.AddCommand(userFindCmd)
}
@ -242,7 +242,7 @@ func init() {
Run: userDeleteF,
}
userDeleteCmd.Flags().StringVarP(&userDeleteFlags.id, "id", "i", "", "user id (required)")
userDeleteCmd.Flags().StringVarP(&userDeleteFlags.id, "id", "i", "", "The user ID (required)")
userDeleteCmd.MarkFlagRequired("id")
userCmd.AddCommand(userDeleteCmd)

View File

@ -18,9 +18,9 @@ import (
var writeCmd = &cobra.Command{
Use: "write line protocol or @/path/to/points.txt",
Short: "Write points to influxdb",
Long: `Write a single line of line protocol to influx db,
or add an entire file specified with an @ prefix`,
Short: "Write points to InfluxDB",
Long: `Write a single line of line protocol to InfluxDB,
or add an entire file specified with an @ prefix.`,
Args: cobra.ExactArgs(1),
RunE: fluxWriteF,
}
@ -34,31 +34,31 @@ var writeFlags struct {
}
func init() {
writeCmd.PersistentFlags().StringVar(&writeFlags.OrgID, "org-id", "", "id of the organization that owns the bucket")
writeCmd.PersistentFlags().StringVar(&writeFlags.OrgID, "org-id", "", "The ID of the organization that owns the bucket")
viper.BindEnv("ORG_ID")
if h := viper.GetString("ORG_ID"); h != "" {
writeFlags.OrgID = h
}
writeCmd.PersistentFlags().StringVarP(&writeFlags.Org, "org", "o", "", "name of the organization that owns the bucket")
writeCmd.PersistentFlags().StringVarP(&writeFlags.Org, "org", "o", "", "The name of the organization that owns the bucket")
viper.BindEnv("ORG")
if h := viper.GetString("ORG"); h != "" {
writeFlags.Org = h
}
writeCmd.PersistentFlags().StringVar(&writeFlags.BucketID, "bucket-id", "", "ID of destination bucket")
writeCmd.PersistentFlags().StringVar(&writeFlags.BucketID, "bucket-id", "", "The ID of destination bucket")
viper.BindEnv("BUCKET_ID")
if h := viper.GetString("BUCKET_ID"); h != "" {
writeFlags.BucketID = h
}
writeCmd.PersistentFlags().StringVarP(&writeFlags.Bucket, "bucket", "b", "", "name of destination bucket")
writeCmd.PersistentFlags().StringVarP(&writeFlags.Bucket, "bucket", "b", "", "The name of destination bucket")
viper.BindEnv("BUCKET_NAME")
if h := viper.GetString("BUCKET_NAME"); h != "" {
writeFlags.Bucket = h
}
writeCmd.PersistentFlags().StringVarP(&writeFlags.Precision, "precision", "p", "ns", "precision of the timestamps of the lines")
writeCmd.PersistentFlags().StringVarP(&writeFlags.Precision, "precision", "p", "ns", "Precision of the timestamps of the lines")
viper.BindEnv("PRECISION")
if p := viper.GetString("PRECISION"); p != "" {
writeFlags.Precision = p

View File

@ -377,9 +377,9 @@ func (e *Engine) DeleteBucket(orgID, bucketID platform.ID) error {
// TODO(edd): we need to clean up how we're encoding the prefix so that we
// don't have to remember to get it right everywhere we need to touch TSM data.
encoded := tsdb.EncodeName(orgID, bucketID)
prefix := models.EscapeMeasurement(encoded[:])
name := models.EscapeMeasurement(encoded[:])
return e.engine.DeletePrefix(prefix, math.MinInt64, math.MaxInt64)
return e.engine.DeleteBucket(name, math.MinInt64, math.MaxInt64)
}
// DeleteSeriesRangeWithPredicate deletes all series data iterated over if fn returns

View File

@ -1,12 +1,14 @@
package storage_test
import (
"fmt"
"io/ioutil"
"math"
"os"
"testing"
"time"
platform "github.com/influxdata/influxdb"
"github.com/influxdata/influxdb"
"github.com/influxdata/influxdb/models"
"github.com/influxdata/influxdb/storage"
"github.com/influxdata/influxdb/tsdb"
@ -149,6 +151,68 @@ func TestEngine_WriteAddNewField(t *testing.T) {
}
}
func TestEngine_DeleteBucket(t *testing.T) {
engine := NewDefaultEngine()
defer engine.Close()
engine.MustOpen()
pt := models.MustNewPoint(
"cpu",
models.NewTags(map[string]string{"host": "server"}),
map[string]interface{}{"value": 1.0},
time.Unix(1, 2),
)
err := engine.Write1xPoints([]models.Point{pt})
if err != nil {
t.Fatalf(err.Error())
}
pt = models.MustNewPoint(
"cpu",
models.NewTags(map[string]string{"host": "server"}),
map[string]interface{}{"value": 1.0, "value2": 2.0},
time.Unix(1, 3),
)
// Same org, different bucket.
err = engine.Write1xPointsWithOrgBucket([]models.Point{pt}, "3131313131313131", "8888888888888888")
if err != nil {
t.Fatalf(err.Error())
}
if got, exp := engine.SeriesCardinality(), int64(3); got != exp {
t.Fatalf("got %d series, exp %d series in index", got, exp)
}
// Remove the original bucket.
if err := engine.DeleteBucket(engine.org, engine.bucket); err != nil {
t.Fatal(err)
}
// Check only one bucket was removed.
if got, exp := engine.SeriesCardinality(), int64(2); got != exp {
t.Fatalf("got %d series, exp %d series in index", got, exp)
}
}
func TestEngine_OpenClose(t *testing.T) {
engine := NewDefaultEngine()
engine.MustOpen()
if err := engine.Close(); err != nil {
t.Fatal(err)
}
if err := engine.Open(); err != nil {
t.Fatal(err)
}
if err := engine.Close(); err != nil {
t.Fatal(err)
}
}
// Ensures that when a shard is closed, it removes any series meta-data
// from the index.
func TestEngineClose_RemoveIndex(t *testing.T) {
@ -201,8 +265,53 @@ func TestEngine_WALDisabled(t *testing.T) {
}
}
func BenchmarkDeleteBucket(b *testing.B) {
var engine *Engine
setup := func(card int) {
engine = NewDefaultEngine()
engine.MustOpen()
points := make([]models.Point, card)
for i := 0; i < card; i++ {
points[i] = models.MustNewPoint(
"cpu",
models.NewTags(map[string]string{"host": "server"}),
map[string]interface{}{"value": i},
time.Unix(1, 2),
)
}
if err := engine.Write1xPoints(points); err != nil {
panic(err)
}
}
for i := 1; i <= 5; i++ {
card := int(math.Pow10(i))
b.Run(fmt.Sprintf("cardinality_%d", card), func(b *testing.B) {
setup(card)
for i := 0; i < b.N; i++ {
if err := engine.DeleteBucket(engine.org, engine.bucket); err != nil {
b.Fatal(err)
}
b.StopTimer()
if err := engine.Close(); err != nil {
panic(err)
}
setup(card)
b.StartTimer()
}
})
}
}
type Engine struct {
path string
path string
org, bucket influxdb.ID
*storage.Engine
}
@ -211,8 +320,21 @@ func NewEngine(c storage.Config) *Engine {
path, _ := ioutil.TempDir("", "storage_engine_test")
engine := storage.NewEngine(path, c)
org, err := influxdb.IDFromString("3131313131313131")
if err != nil {
panic(err)
}
bucket, err := influxdb.IDFromString("3232323232323232")
if err != nil {
panic(err)
}
return &Engine{
path: path,
org: *org,
bucket: *bucket,
Engine: engine,
}
}
@ -233,9 +355,26 @@ func (e *Engine) MustOpen() {
// This allows us to use the old `models` package helper functions and still write
// the points in the correct format.
func (e *Engine) Write1xPoints(pts []models.Point) error {
org, _ := platform.IDFromString("3131313131313131")
bucket, _ := platform.IDFromString("3232323232323232")
points, err := tsdb.ExplodePoints(*org, *bucket, pts)
points, err := tsdb.ExplodePoints(e.org, e.bucket, pts)
if err != nil {
return err
}
return e.Engine.WritePoints(points)
}
// Write1xPointsWithOrgBucket writes 1.x points with the provided org and bucket id strings.
func (e *Engine) Write1xPointsWithOrgBucket(pts []models.Point, org, bucket string) error {
o, err := influxdb.IDFromString(org)
if err != nil {
return err
}
b, err := influxdb.IDFromString(bucket)
if err != nil {
return err
}
points, err := tsdb.ExplodePoints(*o, *b, pts)
if err != nil {
return err
}

View File

@ -609,7 +609,7 @@ func DifferenceSeriesIDIterators(itr0, itr1 SeriesIDIterator) SeriesIDIterator {
if a := NewSeriesIDSetIterators([]SeriesIDIterator{itr0, itr1}); a != nil {
itr0.Close()
itr1.Close()
return NewSeriesIDSetIterator(a[0].SeriesIDSet().AndNot(a[1].SeriesIDSet()))
return NewSeriesIDSetIterator(NewSeriesIDSetNegate(a[0].SeriesIDSet(), a[1].SeriesIDSet()))
}
return &seriesIDDifferenceIterator{itrs: [2]SeriesIDIterator{itr0, itr1}}

View File

@ -27,6 +27,17 @@ func NewSeriesIDSet(a ...SeriesID) *SeriesIDSet {
return ss
}
// NewSeriesIDSetNegate returns a new SeriesIDSet containing all the elements in a
// that are not present in b. That is, the set difference between a and b.
func NewSeriesIDSetNegate(a, b *SeriesIDSet) *SeriesIDSet {
a.RLock()
defer a.RUnlock()
b.RLock()
defer b.RUnlock()
return &SeriesIDSet{bitmap: roaring.AndNot(a.bitmap, b.bitmap)}
}
// Bytes estimates the memory footprint of this SeriesIDSet, in bytes.
func (s *SeriesIDSet) Bytes() int {
var b int
@ -170,15 +181,13 @@ func (s *SeriesIDSet) And(other *SeriesIDSet) *SeriesIDSet {
return &SeriesIDSet{bitmap: roaring.And(s.bitmap, other.bitmap)}
}
// AndNot returns a new SeriesIDSet containing elements that were present in s,
// but not present in other.
func (s *SeriesIDSet) AndNot(other *SeriesIDSet) *SeriesIDSet {
// RemoveSet removes all values in other from s, if they exist.
func (s *SeriesIDSet) RemoveSet(other *SeriesIDSet) {
s.RLock()
defer s.RUnlock()
other.RLock()
defer other.RUnlock()
return &SeriesIDSet{bitmap: roaring.AndNot(s.bitmap, other.bitmap)}
s.bitmap.AndNot(other.bitmap)
}
// ForEach calls f for each id in the set. The function is applied to the IDs

View File

@ -10,7 +10,7 @@ import (
"testing"
)
func TestSeriesIDSet_AndNot(t *testing.T) {
func TestSeriesIDSet_NewSeriesIDSetNegate(t *testing.T) {
examples := [][3][]uint64{
[3][]uint64{
{1, 10, 20, 30},
@ -55,7 +55,7 @@ func TestSeriesIDSet_AndNot(t *testing.T) {
expected.Add(NewSeriesID(v))
}
got := a.AndNot(b)
got := NewSeriesIDSetNegate(a, b)
if got.String() != expected.String() {
t.Fatalf("got %s, expected %s", got.String(), expected.String())
}
@ -63,6 +63,59 @@ func TestSeriesIDSet_AndNot(t *testing.T) {
}
}
func TestSeriesIDSet_RemoveSet(t *testing.T) {
examples := [][3][]uint64{
[3][]uint64{
{1, 10, 20, 30},
{10, 12, 13, 14, 20},
{1, 30},
},
[3][]uint64{
{},
{10},
{},
},
[3][]uint64{
{1, 10, 20, 30},
{1, 10, 20, 30},
{},
},
[3][]uint64{
{1, 10},
{1, 10, 100},
{},
},
[3][]uint64{
{1, 10},
{},
{1, 10},
},
}
for i, example := range examples {
t.Run(fmt.Sprint(i), func(t *testing.T) {
// Build sets.
a, b := NewSeriesIDSet(), NewSeriesIDSet()
for _, v := range example[0] {
a.Add(NewSeriesID(v))
}
for _, v := range example[1] {
b.Add(NewSeriesID(v))
}
expected := NewSeriesIDSet()
for _, v := range example[2] {
expected.Add(NewSeriesID(v))
}
a.RemoveSet(b)
if a.String() != expected.String() {
t.Fatalf("got %s, expected %s", a.String(), expected.String())
}
})
}
}
// Ensure that cloning is race-free.
func TestSeriesIDSet_Clone_Race(t *testing.T) {
main := NewSeriesIDSet()
@ -556,6 +609,78 @@ func BenchmarkSeriesIDSet_Remove(b *testing.B) {
})
}
// BenchmarkSeriesIDSet_MassRemove benchmarks the cost of removing a large set of values.
func BenchmarkSeriesIDSet_MassRemove(b *testing.B) {
var size = uint64(1000000)
// Setup...
set = NewSeriesIDSet()
for i := uint64(0); i < size; i++ {
set.Add(NewSeriesID(i))
}
// Remove one at a time
b.Run(fmt.Sprint("cardinality_1000000_remove_each"), func(b *testing.B) {
clone := set.Clone()
for i := 0; i < b.N; i++ {
for j := uint64(0); j < size/2; j++ {
clone.RemoveNoLock(NewSeriesID(j))
}
b.StopTimer()
clone = set.Clone()
b.StartTimer()
}
})
// This is the case where a target series id set exists.
b.Run(fmt.Sprint("cardinality_1000000_remove_set_exists"), func(b *testing.B) {
clone := set.Clone()
other := NewSeriesIDSet()
for j := uint64(0); j < size/2; j++ {
other.AddNoLock(NewSeriesID(j))
}
for i := 0; i < b.N; i++ {
clone.RemoveSet(other)
b.StopTimer()
clone = set.Clone()
b.StartTimer()
}
})
// Make a target series id set and negate it
b.Run(fmt.Sprint("cardinality_1000000_remove_set"), func(b *testing.B) {
clone := set.Clone()
for i := 0; i < b.N; i++ {
other := NewSeriesIDSet()
for j := uint64(0); j < size/2; j++ {
other.AddNoLock(NewSeriesID(j))
}
clone.RemoveSet(other)
b.StopTimer()
clone = set.Clone()
b.StartTimer()
}
})
// This is the case where a new result set is created.
b.Run(fmt.Sprint("cardinality_1000000_remove_set_new"), func(b *testing.B) {
clone := set.Clone()
other := NewSeriesIDSet()
for j := uint64(0); j < size/2; j++ {
other.AddNoLock(NewSeriesID(j))
}
for i := 0; i < b.N; i++ {
_ = NewSeriesIDSetNegate(clone, other)
b.StopTimer()
clone = set.Clone()
b.StartTimer()
}
})
}
// Typical benchmarks for a laptop:
//
// BenchmarkSeriesIDSet_Merge_Duplicates/cardinality_1/shards_1-4 200000 8095 ns/op 16656 B/op 11 allocs/op

View File

@ -153,6 +153,13 @@ func (c *TagValueSeriesIDCache) Delete(name, key, value []byte, x tsdb.SeriesID)
c.Unlock()
}
// DeleteMeasurement removes all cached entries for the provided measurement name.
func (c *TagValueSeriesIDCache) DeleteMeasurement(name []byte) {
c.Lock()
delete(c.cache, string(name))
c.Unlock()
}
// delete removes x from the tuple {name, key, value} if it exists.
func (c *TagValueSeriesIDCache) delete(name, key, value []byte, x tsdb.SeriesID) {
if mmap, ok := c.cache[string(name)]; ok {

View File

@ -387,7 +387,7 @@ func (fs *FileSet) TagValueSeriesIDIterator(name, key, value []byte) (tsdb.Serie
// Remove tombstones set in previous file.
if ftss != nil && ftss.Cardinality() > 0 {
ss = ss.AndNot(ftss)
ss.RemoveSet(ftss)
}
// Fetch tag value series set for this file and merge into overall set.

View File

@ -592,13 +592,15 @@ func (i *Index) DropMeasurement(name []byte) error {
}()
}
// Remove any cached bitmaps for the measurement.
i.tagValueCache.DeleteMeasurement(name)
// Check for error
for i := 0; i < cap(errC); i++ {
if err := <-errC; err != nil {
return err
}
}
return nil
}

View File

@ -593,6 +593,24 @@ func (f *LogFile) DeleteSeriesID(id tsdb.SeriesID) error {
return f.FlushAndSync()
}
// DeleteSeriesIDList marks a tombstone for all the series IDs. DeleteSeriesIDList
// should be preferred to repeatedly calling DeleteSeriesID for many series ids.
func (f *LogFile) DeleteSeriesIDList(ids []tsdb.SeriesID) error {
f.mu.Lock()
defer f.mu.Unlock()
for _, id := range ids {
e := LogEntry{Flag: LogEntrySeriesTombstoneFlag, SeriesID: id}
if err := f.appendEntry(&e); err != nil {
return err
}
f.execEntry(&e)
}
// Flush buffer and sync to disk.
return f.FlushAndSync()
}
// SeriesN returns the total number of series in the file.
func (f *LogFile) SeriesN() (n uint64) {
f.mu.RLock()

View File

@ -626,8 +626,14 @@ func (p *Partition) DropMeasurement(name []byte) error {
}
// Delete all series.
// TODO(edd): it's not clear to me why we have to delete all series IDs from
// the index when we could just mark the measurement as deleted.
if itr := fs.MeasurementSeriesIDIterator(name); itr != nil {
defer itr.Close()
// 1024 is assuming that typically a bucket (measurement) will have at least
// 1024 series in it.
all := make([]tsdb.SeriesID, 0, 1024)
for {
elem, err := itr.Next()
if err != nil {
@ -635,10 +641,19 @@ func (p *Partition) DropMeasurement(name []byte) error {
} else if elem.SeriesID.IsZero() {
break
}
if err := p.activeLogFile.DeleteSeriesID(elem.SeriesID); err != nil {
return err
}
all = append(all, elem.SeriesID)
// Update series set.
p.seriesIDSet.Remove(elem.SeriesID)
}
if err := p.activeLogFile.DeleteSeriesIDList(all); err != nil {
return err
}
p.tracker.AddSeriesDropped(uint64(len(all)))
p.tracker.SubSeries(uint64(len(all)))
if err = itr.Close(); err != nil {
return err
}

View File

@ -7,10 +7,14 @@ import (
"github.com/influxdata/influxdb/models"
"github.com/influxdata/influxdb/pkg/bytesutil"
"github.com/influxdata/influxdb/tsdb"
"github.com/influxdata/influxql"
)
func (e *Engine) DeletePrefix(prefix []byte, min, max int64) error {
// DeleteBucket removes all TSM data belonging to a bucket, and removes all index
// and series file data associated with the bucket. The provided time range ensures
// that only bucket data for that range is removed.
func (e *Engine) DeleteBucket(name []byte, min, max int64) error {
// TODO(jeff): we need to block writes to this prefix while deletes are in progress
// otherwise we can end up in a situation where we have staged data in the cache or
// WAL that was deleted from the index, or worse. This needs to happen at a higher
@ -63,7 +67,7 @@ func (e *Engine) DeletePrefix(prefix []byte, min, max int64) error {
possiblyDead.keys = make(map[string]struct{})
if err := e.FileStore.Apply(func(r TSMFile) error {
return r.DeletePrefix(prefix, min, max, func(key []byte) {
return r.DeletePrefix(name, min, max, func(key []byte) {
possiblyDead.Lock()
possiblyDead.keys[string(key)] = struct{}{}
possiblyDead.Unlock()
@ -79,7 +83,7 @@ func (e *Engine) DeletePrefix(prefix []byte, min, max int64) error {
// ApplySerialEntryFn cannot return an error in this invocation.
_ = e.Cache.ApplyEntryFn(func(k []byte, _ *entry) error {
if bytes.HasPrefix(k, prefix) {
if bytes.HasPrefix(k, name) {
if deleteKeys == nil {
deleteKeys = make([][]byte, 0, 10000)
}
@ -107,10 +111,10 @@ func (e *Engine) DeletePrefix(prefix []byte, min, max int64) error {
possiblyDead.RLock()
defer possiblyDead.RUnlock()
iter := r.Iterator(prefix)
iter := r.Iterator(name)
for i := 0; iter.Next(); i++ {
key := iter.Key()
if !bytes.HasPrefix(key, prefix) {
if !bytes.HasPrefix(key, name) {
break
}
@ -143,6 +147,46 @@ func (e *Engine) DeletePrefix(prefix []byte, min, max int64) error {
// TODO(jeff): it's also important that all of the deletes happen atomically with
// the deletes of the data in the tsm files.
// In this case the entire measurement (bucket) can be removed from the index.
if min == math.MinInt64 && max == math.MaxInt64 {
// Build up a set of series IDs that we need to remove from the series file.
set := tsdb.NewSeriesIDSet()
itr, err := e.index.MeasurementSeriesIDIterator(name)
if err != nil {
return err
}
var elem tsdb.SeriesIDElem
for elem, err = itr.Next(); err != nil; elem, err = itr.Next() {
if elem.SeriesID.IsZero() {
break
}
set.AddNoLock(elem.SeriesID)
}
if err != nil {
return err
} else if err := itr.Close(); err != nil {
return err
}
// Remove the measurement from the index before the series file.
if err := e.index.DropMeasurement(name); err != nil {
return err
}
// Iterate over the series ids we previously extracted from the index
// and remove from the series file.
set.ForEachNoLock(func(id tsdb.SeriesID) {
if err = e.sfile.DeleteSeriesID(id); err != nil {
return
}
})
return err
}
// This is the slow path, when not dropping the entire bucket (measurement)
for key := range possiblyDead.keys {
// TODO(jeff): ugh reduce copies here
keyb := []byte(key)
@ -157,6 +201,7 @@ func (e *Engine) DeletePrefix(prefix []byte, min, max int64) error {
if err := e.index.DropSeries(sid, keyb, true); err != nil {
return err
}
if err := e.sfile.DeleteSeriesID(sid); err != nil {
return err
}

View File

@ -44,7 +44,7 @@ func TestEngine_DeletePrefix(t *testing.T) {
t.Fatalf("series count mismatch: exp %v, got %v", exp, got)
}
if err := e.DeletePrefix([]byte("cpu"), 0, 3); err != nil {
if err := e.DeleteBucket([]byte("cpu"), 0, 3); err != nil {
t.Fatalf("failed to delete series: %v", err)
}
@ -90,7 +90,7 @@ func TestEngine_DeletePrefix(t *testing.T) {
iter.Close()
// Deleting remaining series should remove them from the series.
if err := e.DeletePrefix([]byte("cpu"), 0, 9); err != nil {
if err := e.DeleteBucket([]byte("cpu"), 0, 9); err != nil {
t.Fatalf("failed to delete series: %v", err)
}

View File

@ -0,0 +1,31 @@
// Libraries
import React from 'react'
import {shallow} from 'enzyme'
// Components
import SaveAsButton from 'src/dataExplorer/components/SaveAsButton'
import SaveAsCellForm from 'src/dataExplorer/components/SaveAsCellForm'
const setup = () => {
const wrapper = shallow(<SaveAsButton />)
return {wrapper}
}
describe('SaveAsButton', () => {
const {wrapper} = setup()
describe('rendering', () => {
it('renders', () => {
expect(wrapper.exists()).toBe(true)
expect(wrapper).toMatchSnapshot()
})
})
describe('save as cell form', () => {
it('defaults to save as cell form', () => {
const saveAsCellForm = wrapper.find(SaveAsCellForm)
expect(saveAsCellForm.exists()).toBe(true)
})
})
})

View File

@ -45,11 +45,11 @@ class SaveAsButton extends PureComponent<Props, State> {
icon={IconFont.Export}
text="Save As"
onClick={this.handleShowOverlay}
color={ComponentColor.Success}
color={ComponentColor.Primary}
titleText="Save your query as a Dashboard Cell or a Task"
/>
<OverlayTechnology visible={isOverlayVisible}>
<OverlayContainer>
<OverlayContainer maxWidth={600}>
<OverlayHeading
title="Save As"
onDismiss={this.handleHideOverlay}
@ -61,6 +61,7 @@ class SaveAsButton extends PureComponent<Props, State> {
active={saveAsOption === SaveAsOption.Dashboard}
value={SaveAsOption.Dashboard}
onClick={this.handleSetSaveAsOption}
data-test="cell-radio-button"
>
Dashboard Cell
</Radio.Button>
@ -68,15 +69,13 @@ class SaveAsButton extends PureComponent<Props, State> {
active={saveAsOption === SaveAsOption.Task}
value={SaveAsOption.Task}
onClick={this.handleSetSaveAsOption}
data-test="task-radio-button"
>
Task
</Radio.Button>
</Radio>
</div>
{saveAsOption === SaveAsOption.Dashboard && (
<SaveAsCellForm dismiss={this.handleHideOverlay} />
)}
{saveAsOption === SaveAsOption.Task && <SaveAsTaskForm />}
{this.saveAsForm}
</OverlayBody>
</OverlayContainer>
</OverlayTechnology>
@ -84,6 +83,16 @@ class SaveAsButton extends PureComponent<Props, State> {
)
}
private get saveAsForm(): JSX.Element {
const {saveAsOption} = this.state
if (saveAsOption === SaveAsOption.Dashboard) {
return <SaveAsCellForm dismiss={this.handleHideOverlay} />
} else if (saveAsOption === SaveAsOption.Task) {
return <SaveAsTaskForm dismiss={this.handleHideOverlay} />
}
}
private handleShowOverlay = () => {
this.setState({isOverlayVisible: true})
}

View File

@ -1,7 +1,147 @@
import React from 'react'
// Libraries
import React, {PureComponent, ChangeEvent} from 'react'
import {connect} from 'react-redux'
import _ from 'lodash'
const SaveAsTaskForm = () => {
return <div>Tasks Not implemented</div>
// Components
import TaskForm from 'src/tasks/components/TaskForm'
// Actions
import {
saveNewScript,
setTaskOption,
clearTask,
setNewScript,
} from 'src/tasks/actions/v2'
// Types
import {AppState, Organization} from 'src/types/v2'
import {
TaskSchedule,
TaskOptions,
TaskOptionKeys,
} from 'src/utils/taskOptionsToFluxScript'
import {DashboardDraftQuery} from 'src/types/v2/dashboards'
interface OwnProps {
dismiss: () => void
}
export default SaveAsTaskForm
interface DispatchProps {
saveNewScript: typeof saveNewScript
setTaskOption: typeof setTaskOption
clearTask: typeof clearTask
setNewScript: typeof setNewScript
}
interface StateProps {
orgs: Organization[]
taskOptions: TaskOptions
draftQueries: DashboardDraftQuery[]
activeQueryIndex: number
}
type Props = StateProps & OwnProps & DispatchProps
class SaveAsTaskForm extends PureComponent<Props> {
public componentDidMount() {
const {setTaskOption, setNewScript} = this.props
setTaskOption({
key: 'taskScheduleType',
value: TaskSchedule.interval,
})
setNewScript(this.activeScript)
}
public componentWillUnmount() {
const {clearTask} = this.props
clearTask()
}
public render() {
const {orgs, taskOptions, dismiss} = this.props
return (
<TaskForm
orgs={orgs}
taskOptions={taskOptions}
onChangeScheduleType={this.handleChangeScheduleType}
onChangeInput={this.handleChangeInput}
onChangeTaskOrgID={this.handleChangeTaskOrgID}
isInOverlay={true}
onSubmit={this.handleSubmit}
canSubmit={this.isFormValid}
dismiss={dismiss}
/>
)
}
private get isFormValid(): boolean {
const {
taskOptions: {name, cron, interval},
} = this.props
const hasSchedule = !!cron || !!interval
return hasSchedule && !!name && !!this.activeScript
}
private get activeScript(): string {
const {draftQueries, activeQueryIndex} = this.props
return _.get(draftQueries, `${activeQueryIndex}.text`)
}
private handleSubmit = () => {
const {saveNewScript} = this.props
saveNewScript()
}
private handleChangeTaskOrgID = (orgID: string) => {
const {setTaskOption} = this.props
setTaskOption({key: 'orgID', value: orgID})
}
private handleChangeScheduleType = (taskScheduleType: TaskSchedule) => {
const {setTaskOption} = this.props
setTaskOption({key: 'taskScheduleType', value: taskScheduleType})
}
private handleChangeInput = (e: ChangeEvent<HTMLInputElement>) => {
const {setTaskOption} = this.props
const key = e.target.name as TaskOptionKeys
const value = e.target.value
setTaskOption({key, value})
}
}
const mstp = (state: AppState): StateProps => {
const {
orgs,
tasks,
timeMachines: {
timeMachines: {de},
},
} = state
const {draftQueries, activeQueryIndex} = de
return {orgs, taskOptions: tasks.taskOptions, draftQueries, activeQueryIndex}
}
const mdtp: DispatchProps = {
saveNewScript,
setTaskOption,
clearTask,
setNewScript,
}
export default connect<StateProps, DispatchProps>(
mstp,
mdtp
)(SaveAsTaskForm)

View File

@ -0,0 +1,65 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SaveAsButton rendering renders 1`] = `
<Fragment>
<Button
active={false}
color="primary"
icon="export"
onClick={[Function]}
shape="none"
size="sm"
status="default"
text="Save As"
titleText="Save your query as a Dashboard Cell or a Task"
type="button"
/>
<OverlayTechnology
visible={false}
>
<OverlayContainer
maxWidth={600}
>
<OverlayHeading
onDismiss={[Function]}
title="Save As"
/>
<OverlayBody>
<div
className="save-as--options"
>
<Radio
color="primary"
shape="none"
size="sm"
>
<RadioButton
active={true}
data-test="cell-radio-button"
disabled={false}
disabledTitleText="This option is disabled"
onClick={[Function]}
value="dashboard"
>
Dashboard Cell
</RadioButton>
<RadioButton
active={false}
data-test="task-radio-button"
disabled={false}
disabledTitleText="This option is disabled"
onClick={[Function]}
value="task"
>
Task
</RadioButton>
</Radio>
</div>
<Connect(SaveAsCellForm)
dismiss={[Function]}
/>
</OverlayBody>
</OverlayContainer>
</OverlayTechnology>
</Fragment>
`;

View File

@ -0,0 +1,92 @@
/*
Getting Started Widget
------------------------------------------------------------------------------
*/
@import 'src/style/modules';
$getting-started--gutter: $ix-marg-b;
.getting-started {
margin-top: $ix-marg-b;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.getting-started--container {
width: 100%;
padding-bottom: 100%;
margin-bottom: $getting-started--gutter;
position: relative;
}
.getting-started--card {
position: absolute;
width: 100%;
height: 100%;
background-color: $g4-onyx;
border-radius: $radius;
text-align: center;
padding: $ix-marg-c;
transition: background-color 0.25s ease;
display: flex;
flex-direction: column;
justify-content: space-between;
&:hover {
background-color: $g5-pepper;
cursor: pointer;
.gradient-border {
opacity: 1;
}
}
}
.getting-started--title {
font-weight: 500;
color: $g11-sidewalk;
font-size: 20px;
transition: color 0.25s ease;
margin: 0;
margin-bottom: $ix-marg-e;
.getting-started--card:hover & {
color: $g20-white;
}
}
.getting-started--image {
width: 100%;
height: 70%;
background-size: cover;
> svg {
width: 100%;
height: 100%;
}
}
@media screen and (min-width: $grid--breakpoint-sm) {
.getting-started--container {
width: calc(33.3333% - #{$getting-started--gutter});
padding-bottom: calc(50% - #{$getting-started--gutter});
margin-bottom: 0;
}
.getting-started--title {
font-size: 14px;
margin: 0;
}
}
@media screen and (min-width: $grid--breakpoint-md) {
.getting-started--container {
width: calc(33.3333% - #{$getting-started--gutter});
padding-bottom: calc(33.3333% - #{$getting-started--gutter});
}
.getting-started--title {
font-size: 19px;
margin-bottom: $ix-marg-d;
}
}

View File

@ -0,0 +1,53 @@
// Libraries
import React, {PureComponent} from 'react'
import {Link} from 'react-router'
// Components
import GradientBorder from 'src/shared/components/cells/GradientBorder'
import DashboardingGraphic from 'src/me/graphics/DashboardingGraphic'
import ExploreGraphic from 'src/me/graphics/ExploreGraphic'
import CollectorGraphic from 'src/me/graphics/CollectorGraphic'
// Styles
import 'src/me/components/GettingStarted.scss'
export default class GettingStarted extends PureComponent {
public render() {
return (
<div className="getting-started">
<div className="getting-started--container">
<Link to={`/data-explorer`} className="getting-started--card">
<GradientBorder />
<CollectorGraphic />
<h3 className="getting-started--title">
Configure a<br />
Data Collector
</h3>
</Link>
</div>
<div className="getting-started--container">
<Link to={`/dashboards`} className="getting-started--card">
<GradientBorder />
<DashboardingGraphic />
<h3 className="getting-started--title">
Build a Monitoring
<br />
Dashboard
</h3>
</Link>
</div>
<div className="getting-started--container">
<Link to={`/data-explorer`} className="getting-started--card">
<GradientBorder />
<ExploreGraphic />
<h3 className="getting-started--title">
Explore your data
<br />
using Flux
</h3>
</Link>
</div>
</div>
)
}
}

View File

@ -4,21 +4,31 @@ import React, {PureComponent} from 'react'
// Components
import {Page} from 'src/pageLayout'
// Constants
import {generateRandomGreeting} from 'src/me/constants'
interface Props {
title: string
userName: string
}
export default class UserPageHeader extends PureComponent<Props> {
public render() {
const {title} = this.props
return (
<Page.Header fullWidth={false}>
<Page.Header.Left>
<Page.Title title={title} />
</Page.Header.Left>
<Page.Header.Left>{this.title}</Page.Header.Left>
<Page.Header.Right />
</Page.Header>
)
}
private get title(): JSX.Element {
const {userName} = this.props
const {text, language} = generateRandomGreeting()
const title = `${text}, ${userName}!`
const altText = `That's how you say hello in ${language}`
return <Page.Title title={title} altText={altText} />
}
}

View File

@ -15,48 +15,8 @@ import {Authorization, Permission} from 'src/api'
// Actions
import {NotificationAction} from 'src/types'
const {Orgs, Users, Buckets, Tasks} = Permission.ResourceEnum
const {Write, Read} = Permission.ActionEnum
export interface TestPermission {
resource: Permission.ResourceEnum
actions: Permission.ActionEnum[]
id?: string
name?: string
orgID?: string
orgName?: string
}
const testPerms: TestPermission[] = [
{
resource: Users,
actions: [Write, Read],
},
{
resource: Orgs,
id: '1',
name: 'myorg',
actions: [Read],
},
{
resource: Buckets,
id: '2',
name: 'telegraf',
actions: [Read],
},
{
resource: Tasks, // resource will be Task `task`
name: 'task1',
actions: [Read],
},
{
resource: Tasks, // resource will be Task `task`
id: '2',
name: 'task1',
actions: [Read],
},
]
interface Props {
onNotify: NotificationAction
auth: Authorization
@ -67,7 +27,7 @@ const actions = [Read, Write]
export default class ViewTokenOverlay extends PureComponent<Props> {
public render() {
const {description} = this.props.auth
const {description, permissions} = this.props.auth
const {onNotify} = this.props
return (
@ -79,7 +39,7 @@ export default class ViewTokenOverlay extends PureComponent<Props> {
mode={PermissionsWidgetMode.Read}
heightPixels={500}
>
{testPerms.map((p, i) => {
{permissions.map((p, i) => {
return (
<PermissionsWidget.Section
key={i}
@ -105,12 +65,10 @@ export default class ViewTokenOverlay extends PureComponent<Props> {
}
private selected = (
permission: TestPermission,
permission: Permission,
action: Permission.ActionEnum
): PermissionsWidgetSelection => {
const isSelected = permission.actions.some(a => a === action)
if (isSelected) {
if (permission.action === action) {
return PermissionsWidgetSelection.Selected
}
@ -118,13 +76,13 @@ export default class ViewTokenOverlay extends PureComponent<Props> {
}
private itemID = (
permission: TestPermission,
permission: Permission,
action: Permission.ActionEnum
): string => {
return `${permission.id || permission.resource}-${action}`
}
private id = (permission: TestPermission): string => {
private id = (permission: Permission): string => {
return permission.id || permission.resource
}

View File

@ -283,10 +283,10 @@ exports[`Account rendering renders! 1`] = `
}
>
<PermissionsWidgetSection
id="users"
id="orgs"
key=".$0"
mode="read"
title="users:*"
title="orgs:*"
>
<section
className="permissions-widget--section"
@ -297,28 +297,28 @@ exports[`Account rendering renders! 1`] = `
<h3
className="permissions-widget--section-title"
>
users:*
orgs:*
</h3>
</header>
<ul
className="permissions-widget--section-list"
>
<PermissionsWidgetItem
id="users-read"
id="orgs-read"
key=".$0"
label="read"
mode="read"
selected="selected"
selected="unselected"
>
<li
className="permissions-widget--item selected"
className="permissions-widget--item unselected"
onClick={[Function]}
>
<div
className="permissions-widget--icon"
>
<span
className="icon checkmark"
className="icon remove"
/>
</div>
<label
@ -329,7 +329,7 @@ exports[`Account rendering renders! 1`] = `
</li>
</PermissionsWidgetItem>
<PermissionsWidgetItem
id="users-write"
id="orgs-write"
key=".$1"
label="write"
mode="read"
@ -357,10 +357,10 @@ exports[`Account rendering renders! 1`] = `
</section>
</PermissionsWidgetSection>
<PermissionsWidgetSection
id="1"
id="buckets"
key=".$1"
mode="read"
title="orgs:myorg"
title="buckets:*"
>
<section
className="permissions-widget--section"
@ -371,42 +371,17 @@ exports[`Account rendering renders! 1`] = `
<h3
className="permissions-widget--section-title"
>
orgs:myorg
buckets:*
</h3>
</header>
<ul
className="permissions-widget--section-list"
>
<PermissionsWidgetItem
id="1-read"
id="buckets-read"
key=".$0"
label="read"
mode="read"
selected="selected"
>
<li
className="permissions-widget--item selected"
onClick={[Function]}
>
<div
className="permissions-widget--icon"
>
<span
className="icon checkmark"
/>
</div>
<label
className="permissions-widget--item-label"
>
read
</label>
</li>
</PermissionsWidgetItem>
<PermissionsWidgetItem
id="1-write"
key=".$1"
label="write"
mode="read"
selected="unselected"
>
<li
@ -423,38 +398,14 @@ exports[`Account rendering renders! 1`] = `
<label
className="permissions-widget--item-label"
>
write
read
</label>
</li>
</PermissionsWidgetItem>
</ul>
</section>
</PermissionsWidgetSection>
<PermissionsWidgetSection
id="2"
key=".$2"
mode="read"
title="buckets:telegraf"
>
<section
className="permissions-widget--section"
>
<header
className="permissions-widget--section-heading"
>
<h3
className="permissions-widget--section-title"
>
buckets:telegraf
</h3>
</header>
<ul
className="permissions-widget--section-list"
>
<PermissionsWidgetItem
id="2-read"
key=".$0"
label="read"
id="buckets-write"
key=".$1"
label="write"
mode="read"
selected="selected"
>
@ -469,179 +420,6 @@ exports[`Account rendering renders! 1`] = `
className="icon checkmark"
/>
</div>
<label
className="permissions-widget--item-label"
>
read
</label>
</li>
</PermissionsWidgetItem>
<PermissionsWidgetItem
id="2-write"
key=".$1"
label="write"
mode="read"
selected="unselected"
>
<li
className="permissions-widget--item unselected"
onClick={[Function]}
>
<div
className="permissions-widget--icon"
>
<span
className="icon remove"
/>
</div>
<label
className="permissions-widget--item-label"
>
write
</label>
</li>
</PermissionsWidgetItem>
</ul>
</section>
</PermissionsWidgetSection>
<PermissionsWidgetSection
id="tasks"
key=".$3"
mode="read"
title="tasks:task1"
>
<section
className="permissions-widget--section"
>
<header
className="permissions-widget--section-heading"
>
<h3
className="permissions-widget--section-title"
>
tasks:task1
</h3>
</header>
<ul
className="permissions-widget--section-list"
>
<PermissionsWidgetItem
id="tasks-read"
key=".$0"
label="read"
mode="read"
selected="selected"
>
<li
className="permissions-widget--item selected"
onClick={[Function]}
>
<div
className="permissions-widget--icon"
>
<span
className="icon checkmark"
/>
</div>
<label
className="permissions-widget--item-label"
>
read
</label>
</li>
</PermissionsWidgetItem>
<PermissionsWidgetItem
id="tasks-write"
key=".$1"
label="write"
mode="read"
selected="unselected"
>
<li
className="permissions-widget--item unselected"
onClick={[Function]}
>
<div
className="permissions-widget--icon"
>
<span
className="icon remove"
/>
</div>
<label
className="permissions-widget--item-label"
>
write
</label>
</li>
</PermissionsWidgetItem>
</ul>
</section>
</PermissionsWidgetSection>
<PermissionsWidgetSection
id="2"
key=".$4"
mode="read"
title="tasks:task1"
>
<section
className="permissions-widget--section"
>
<header
className="permissions-widget--section-heading"
>
<h3
className="permissions-widget--section-title"
>
tasks:task1
</h3>
</header>
<ul
className="permissions-widget--section-list"
>
<PermissionsWidgetItem
id="2-read"
key=".$0"
label="read"
mode="read"
selected="selected"
>
<li
className="permissions-widget--item selected"
onClick={[Function]}
>
<div
className="permissions-widget--icon"
>
<span
className="icon checkmark"
/>
</div>
<label
className="permissions-widget--item-label"
>
read
</label>
</li>
</PermissionsWidgetItem>
<PermissionsWidgetItem
id="2-write"
key=".$1"
label="write"
mode="read"
selected="unselected"
>
<li
className="permissions-widget--item unselected"
onClick={[Function]}
>
<div
className="permissions-widget--icon"
>
<span
className="icon remove"
/>
</div>
<label
className="permissions-widget--item-label"
>

View File

@ -0,0 +1,157 @@
import _ from 'lodash'
interface Greeting {
text: string
language: string
}
const randomGreetings: Greeting[] = [
{
text: 'Greetings',
language: 'English',
},
{
text: 'Ahoy',
language: 'Pirate',
},
{
text: 'Howdy',
language: 'Texas',
},
{
text: 'Bonjour',
language: 'French',
},
{
text: 'Hola',
language: 'Spanish',
},
{
text: 'Ciao',
language: 'Italian',
},
{
text: 'Hallo',
language: 'German',
},
{
text: 'Guten Tag',
language: 'German',
},
{
text: 'Olà',
language: 'Portuguese',
},
{
text: 'Namaste',
language: 'Hindi',
},
{
text: 'Salaam',
language: 'Farsi',
},
{
text: 'Ohayo',
language: 'Japanese',
},
{
text: 'こんにちは',
language: 'Japanese',
},
{
text: 'Merhaba',
language: 'Turkish',
},
{
text: 'Szia',
language: 'Hungarian',
},
{
text: 'Jambo',
language: 'Swahili',
},
{
text: '你好',
language: 'Chinese (Simplified)',
},
{
text: 'مرحبا',
language: 'Arabic',
},
{
text: 'Բարեւ',
language: 'Armenian',
},
{
text: 'Zdravo',
language: 'Croatian',
},
{
text: 'Привет',
language: 'Russian',
},
{
text: 'Xin chào',
language: 'Vietnamese',
},
{
text: 'สวัสดี',
language: 'Thai',
},
{
text: 'สวัสดี',
language: 'Thai',
},
{
text: 'Dzień dobry',
language: 'Polish',
},
{
text: 'Hei',
language: 'Finnish',
},
{
text: 'γεια σας',
language: 'Greek',
},
{
text: '인사말',
language: 'Korean',
},
{
text: 'Salve',
language: 'Latin',
},
{
text: 'Cyfarchion',
language: 'Welsh',
},
{
text: 'Ukubingelela',
language: 'Zulu',
},
{
text: 'Beannachtaí',
language: 'Irish',
},
{
text: '01001000 01100101 01101100 01101100 01101111',
language: 'Binary',
},
{
text: '.... . .-.. .-.. ---',
language: 'Morse Code',
},
{
text: 'nuqneH',
language: 'Klingon',
},
{
text: 'Saluton',
language: 'Esperanto',
},
]
export const generateRandomGreeting = (): Greeting => {
return _.sample(randomGreetings)
}

View File

@ -6,10 +6,12 @@ import {connect} from 'react-redux'
import 'src/me/containers/MePage.scss'
// Components
import {Grid, Columns} from 'src/clockface'
import {Page} from 'src/pageLayout'
import Resources from 'src/me/components/Resources'
import Header from 'src/me/components/UserPageHeader'
import Docs from 'src/me/components/Docs'
import GettingStarted from 'src/me/components/GettingStarted'
// Types
import {MeState, AppState} from 'src/types/v2'
@ -30,19 +32,25 @@ export class MePage extends PureComponent<StateProps> {
return (
<Page className="user-page" titleTag="My Account">
<Header title={`Howdy, ${me.name}!`} />
<Header userName={me.name} />
<Page.Contents fullWidth={false} scrollable={true}>
<div className="col-xs-8">
<Panel>
<Panel.Header title="Getting Started" />
<Panel.Body>
<span>Put Getting Started Stuff Here</span>
</Panel.Body>
</Panel>
<Docs />
</div>
<div className="col-xs-4">
<Resources me={me} />
<div className="col-xs-12">
<Grid>
<Grid.Row>
<Grid.Column widthSM={Columns.Eight} widthMD={Columns.Nine}>
<Panel>
<Panel.Header title="Getting started with InfluxDB 2.0" />
<Panel.Body>
<GettingStarted />
</Panel.Body>
</Panel>
<Docs />
</Grid.Column>
<Grid.Column widthSM={Columns.Four} widthMD={Columns.Three}>
<Resources me={me} />
</Grid.Column>
</Grid.Row>
</Grid>
</div>
</Page.Contents>
</Page>

View File

@ -0,0 +1,138 @@
/*
Getting Started Collector Graphic
------------------------------------------------------------------------------
*/
@import 'src/style/modules';
$collector-graphic-anim-time: 0.5s;
.collector-graphic--bg {
fill: $g2-kevlar;
transition: fill 0.25s ease;
}
.collector-graphic--dot {
fill: $g5-pepper;
transition: fill $collector-graphic-anim-time ease;
}
.collector-graphic--bucket {
fill: $g5-pepper;
transition: fill 0.25s ease;
}
.collector-graphic--bucket-hole {
fill: $g2-kevlar;
transition: fill 0.25s ease;
}
.collector-graphic--data {
fill: none;
stroke-width: 2;
stroke-linecap: round;
stroke-miterlimit: 10;
transition: all $collector-graphic-anim-time ease;
}
.data-a,
.data-b,
.data-c,
.data-d,
.data-e {
stroke-width: 2px;
}
.data-a {
stroke: $c-rainforest;
stroke-dasharray: 146;
stroke-dashoffset: 146;
}
.data-b {
stroke: $c-thunder;
stroke-dasharray: 102;
stroke-dashoffset: 102;
}
.data-c {
stroke: $c-pool;
stroke-dasharray: 81;
stroke-dashoffset: 81;
}
.data-d {
stroke: $c-comet;
stroke-dasharray: 102;
stroke-dashoffset: 102;
}
.data-e {
stroke: $c-dreamsicle;
stroke-dasharray: 146;
stroke-dashoffset: 146;
}
@keyframes cubo-spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.collector-graphic--cubo {
animation: cubo-spin 5s linear infinite;
transform-origin: 161px 161.6px;
transition: opacity $collector-graphic-anim-time ease;
opacity: 0;
}
.collector-graphic--cubo-line {
transition: stroke 0.25s ease;
stroke-width: 1.5px;
stroke: $g2-kevlar;
fill: none;
}
/* Hover State */
.getting-started--card:hover {
.collector-graphic--bucket {
fill: $g13-mist;
}
.collector-graphic--bucket-hole {
fill: $g9-mountain;
}
.collector-graphic--bg {
fill: $g3-castle;
}
.dot-a {
fill: $c-rainforest;
}
.dot-b {
fill: $c-thunder;
}
.dot-c {
fill: $c-pool;
}
.dot-d {
fill: $c-comet;
}
.dot-e {
fill: $c-dreamsicle;
}
.data-a,
.data-b,
.data-c,
.data-d,
.data-e {
stroke-dashoffset: 0;
}
.collector-graphic--cubo {
opacity: 1;
}
.collector-graphic--cubo-line {
stroke: $g9-mountain;
}
}

View File

@ -0,0 +1,163 @@
// Libraries
import React, {PureComponent} from 'react'
// Styles
import 'src/me/graphics/CollectorGraphic.scss'
export default class GettingStarted extends PureComponent {
public render() {
return (
<div className="getting-started--image collector-graphic">
<svg
version="1.1"
x="0px"
y="0px"
width="322px"
height="225px"
viewBox="0 0 322 225"
>
<g id="Bucket">
<path
id="BG"
className="collector-graphic--bg"
d="M161,199c-14.1,0-29.2-3.8-29.8-14.4l-6.9-51.3v-0.5c0-13.1,23-15.1,36.7-15.1s36.7,2,36.7,15.1v0.5
l-6.9,51.3C190.2,195.2,175.1,199,161,199z"
/>
<path
id="BucketExterior"
className="collector-graphic--bucket"
d="M132.3,132.8c0-3.9,12.8-7.1,28.7-7.1s28.7,3.2,28.7,7.1l-6.9,51.1
c0,3.9-9.7,7.1-21.8,7.1c-12.1,0-21.8-3.1-21.8-7.1L132.3,132.8z"
/>
<ellipse
id="BucketHole"
className="collector-graphic--bucket-hole"
cx="160.8"
cy="132.8"
rx="25.1"
ry="4.7"
/>
</g>
<g id="Blocks">
<path
className="collector-graphic--bg"
d="M178.2,66.5c0,1.1-0.9,2-2,2h-30.5c-1.1,0-2-0.9-2-2V36c0-1.1,0.9-2,2-2h30.5c1.1,0,2,0.9,2,2V66.5z"
/>
<path
className="collector-graphic--bg"
d="M121.4,66.5c0,1.1-0.9,2-2,2H88.9c-1.1,0-2-0.9-2-2V36c0-1.1,0.9-2,2-2h30.5c1.1,0,2,0.9,2,2V66.5z"
/>
<path
className="collector-graphic--bg"
d="M64.5,66.5c0,1.1-0.9,2-2,2H32c-1.1,0-2-0.9-2-2V36c0-1.1,0.9-2,2-2h30.5c1.1,0,2,0.9,2,2V66.5z"
/>
<path
className="collector-graphic--bg"
d="M235.1,66.5c0,1.1-0.9,2-2,2h-30.5c-1.1,0-2-0.9-2-2V36c0-1.1,0.9-2,2-2h30.5c1.1,0,2,0.9,2,2V66.5z"
/>
<path
className="collector-graphic--bg"
d="M292,66.5c0,1.1-0.9,2-2,2h-30.5c-1.1,0-2-0.9-2-2V36c0-1.1,0.9-2,2-2H290c1.1,0,2,0.9,2,2V66.5z"
/>
</g>
<g id="Lines">
<path
id="LineE"
className="collector-graphic--data data-e"
d="M274.8,58.3c0,63-105.5,35.5-105.5,81.3"
/>
<path
id="LineD"
className="collector-graphic--data data-d"
d="M217.9,58.3c0,10.2-9.4,25.8-23.6,34.5s-29.5,24.4-29.5,46.7"
/>
<line
id="LineC"
className="collector-graphic--data data-c"
x1="161"
y1="58.3"
x2="161"
y2="139.5"
/>
<path
id="LineB"
className="collector-graphic--data data-b"
d="M104.1,58.3c0,10.2,9.4,25.8,23.6,34.5s29.5,24.4,29.5,46.7"
/>
<path
id="LineA"
className="collector-graphic--data data-a"
d="M47.2,58.3c0,63,105.5,35.5,105.5,81.3"
/>
</g>
<g id="Dots">
<circle
id="DotE"
className="collector-graphic--dot dot-e"
cx="274.8"
cy="51.3"
r="7"
/>
<circle
id="DotD"
className="collector-graphic--dot dot-d"
cx="217.9"
cy="51.3"
r="7"
/>
<circle
id="DotC"
className="collector-graphic--dot dot-c"
cx="161"
cy="51.3"
r="7"
/>
<circle
id="DotB"
className="collector-graphic--dot dot-b"
cx="104.1"
cy="51.3"
r="7"
/>
<circle
id="DotA"
className="collector-graphic--dot dot-a"
cx="47.2"
cy="51.3"
r="7"
/>
</g>
<path
id="BucketMask"
className="collector-graphic--bucket"
d="M176.2,126.8v2.3c5.9,0.9,9.7,2.2,9.7,3.7c0,2.6-11.2,4.7-25.1,4.7s-25.1-2.1-25.1-4.7
c0-1.5,3.6-2.8,9.2-3.6V127c-7.7,1.3-12.7,3.4-12.7,5.9l6.9,51.1c0,3.9,9.7,7.1,21.8,7.1s21.8-3.1,21.8-7.1l6.9-51.1
C189.7,130.3,184.3,128.1,176.2,126.8z"
/>
<g id="Cubo" className="collector-graphic--cubo">
<polygon
className="collector-graphic--cubo-line"
points="166.5,150.9 155.1,150.9 149.4,160.8 155.1,170.7 166.5,170.7 172.2,160.8 "
/>
<polygon
className="collector-graphic--cubo-line"
points="155.1,164.1 160.8,154.2 166.5,164.1 "
/>
<polyline
className="collector-graphic--cubo-line"
points="155.1,150.9 160.8,154.2 166.5,150.9 "
/>
<polyline
className="collector-graphic--cubo-line"
points="172.2,160.8 166.5,164.1 166.5,170.7 "
/>
<polyline
className="collector-graphic--cubo-line"
points="155.1,170.7 155.1,164.1 149.4,160.8 "
/>
</g>
</svg>
</div>
)
}
}

View File

@ -0,0 +1,149 @@
/*
Getting Started Dashboard Graphic
------------------------------------------------------------------------------
*/
@import 'src/style/modules';
$dashboarding-graphic-anim-time: 0.5s;
.dashboarding-graphic--bg {
transition: fill 0.25s ease;
fill: $g2-kevlar;
}
.dashboarding-graphic--cell {
fill: $g5-pepper;
transition: fill 0.25s ease;
}
.dashboarding-graphic--single-stat {
fill: $g5-pepper;
transition: fill $dashboarding-graphic-anim-time ease;
}
.dashboarding-graphic--axes {
stroke-width: 1.5px;
stroke: $g5-pepper;
fill: transparent;
transition: fill $dashboarding-graphic-anim-time ease, stroke $dashboarding-graphic-anim-time ease;
stroke-linecap: round;
stroke-linejoin: round;
stroke-miterlimit: 10;
}
.dashboarding-graphic--line {
fill: none;
stroke-width: 1.5px;
stroke: $g5-pepper;
transition: stroke $dashboarding-graphic-anim-time ease;
stroke-linecap: round;
stroke-linejoin: round;
stroke-miterlimit: 10;
}
.line-a,
.line-b,
.line-c,
.line-d {
transition: all $dashboarding-graphic-anim-time ease;
}
.line-a {
stroke-dasharray: 122;
stroke-dashoffset: 122;
}
.line-b {
stroke-dasharray: 161;
stroke-dashoffset: 161;
}
.line-c {
stroke-dasharray: 102;
stroke-dashoffset: 102;
}
.line-d {
stroke-dasharray: 99;
stroke-dashoffset: 99;
}
.dashboarding-graphic--bar {
fill: $g5-pepper;
transition: all $dashboarding-graphic-anim-time ease;
height: 0;
}
/* Hover State */
.getting-started--card:hover {
.dashboarding-graphic--bg {
fill: $g3-castle;
}
.dashboarding-graphic--cell {
fill: $g6-smoke;
}
.single-stat-a {
fill: $c-pool;
}
.single-stat-b {
fill: $c-dreamsicle;
}
.single-stat-c {
fill: $c-honeydew;
}
.single-stat-d {
fill: $c-comet;
}
.table-a {
fill: $c-pool;
}
.table-b {
fill: $c-comet;
}
.line-a,
.line-b,
.line-c,
.line-d {
stroke-dashoffset: 0;
}
.line-a {
stroke: $c-honeydew;
}
.line-b {
stroke: $c-pool;
}
.line-c {
stroke: $c-comet;
}
.line-d {
stroke: $c-thunder;
}
.dashboarding-graphic--axes {
stroke: $g7-graphite;
}
.dashboarding-graphic--bar {
fill: $c-pool;
}
.bar-a {
height: 15.2px;
}
.bar-b {
height: 32.2px;
}
.bar-c {
height: 25.8px;
}
.bar-d {
height: 4px;
}
.bar-e {
height: 2px;
}
.bar-f {
height: 7.6px;
}
.bar-g {
height: 4px;
}
}

View File

@ -0,0 +1,379 @@
// Libraries
import React, {PureComponent} from 'react'
// Styles
import 'src/me/graphics/DashboardingGraphic.scss'
export default class GettingStarted extends PureComponent {
public render() {
return (
<div className="getting-started--image dashboarding-graphic">
<svg
version="1.1"
x="0px"
y="0px"
width="322px"
height="225px"
viewBox="0 0 322 225"
>
<g>
<path
id="Background"
className="dashboarding-graphic--bg"
d="M291,30.5H31c-2.2,0-4,1.8-4,4v156c0,2.2,1.8,4,4,4h260c2.2,0,4-1.8,4-4v-156
C295,32.3,293.2,30.5,291,30.5z"
/>
<g id="Cells">
<path
className="dashboarding-graphic--cell"
d="M162,41.5c0-1.7,1.3-3,3-3h55.5c1.7,0,3,1.3,3,3v23c0,1.7-1.3,3-3,3H165c-1.7,0-3-1.3-3-3V41.5z"
/>
<path
className="dashboarding-graphic--cell"
d="M98.5,41.4c0.1-1.6,1.4-2.9,3-2.9H157c1.7,0,3,1.3,3,3v23c0,1.7-1.3,3-3,3h-55.5c-1.6,0-2.9-1.3-3-2.9
c0,0,0-0.1,0-0.1v-23C98.5,41.5,98.5,41.4,98.5,41.4z"
/>
<path
className="dashboarding-graphic--cell"
d="M35,41.5c0-1.7,1.3-3,3-3h55.5c1.6,0,2.9,1.3,3,2.9c0,0,0,0.1,0,0.1v23c0,0,0,0.1,0,0.1
c-0.1,1.6-1.4,2.9-3,2.9H38c-1.7,0-3-1.3-3-3V41.5z"
/>
<path
className="dashboarding-graphic--cell"
d="M35,72.5c0-1.7,1.3-3,3-3h55.5h8H157c1.7,0,3,1.3,3,3V132c0,1.7-1.3,3-3,3h-33h-8H38c-1.7,0-3-1.3-3-3V72.5z"
/>
<path
className="dashboarding-graphic--cell"
d="M119,183.6c-0.1,1.6-1.4,2.9-3,2.9H38c-1.7,0-3-1.3-3-3V140c0-1.7,1.3-3,3-3h78c1.6,0,2.9,1.3,3,2.9
c0,0,0,0.1,0,0.1v43.5C119,183.5,119,183.6,119,183.6z"
/>
<path
className="dashboarding-graphic--cell"
d="M201,183.6c-0.1,1.6-1.4,2.9-3,2.9h-74c-1.6,0-2.9-1.3-3-2.9c0,0,0-0.1,0-0.1V140c0,0,0-0.1,0-0.1
c0.1-1.6,1.4-2.9,3-2.9h33h8h33c1.6,0,2.9,1.3,3,2.9c0,0,0,0.1,0,0.1v43.5C201,183.5,201,183.6,201,183.6z"
/>
<path
className="dashboarding-graphic--cell"
d="M287,183.5c0,1.7-1.3,3-3,3h-78c-1.6,0-2.9-1.3-3-2.9c0,0,0-0.1,0-0.1V140c0,0,0-0.1,0-0.1
c0.1-1.6,1.4-2.9,3-2.9h78c1.7,0,3,1.3,3,3V183.5z"
/>
<path
className="dashboarding-graphic--cell"
d="M287,64.5c0,1.7-1.3,3-3,3h-55.5c-1.7,0-3-1.3-3-3v-23c0-1.7,1.3-3,3-3H284c1.7,0,3,1.3,3,3V64.5z"
/>
<path
id="Cell"
className="dashboarding-graphic--cell"
d="M287,132c0,1.7-1.3,3-3,3h-78h-8h-33c-1.7,0-3-1.3-3-3V72.5c0-1.7,1.3-3,3-3h0h55.5h8H284
c1.7,0,3,1.3,3,3V132z"
/>
</g>
<g id="Single_Stat_4392">
<path
className="dashboarding-graphic--single-stat single-stat-a"
d="M57.8,54.1H59V55h-1.2v2h-1.1v-2h-3.9v-0.6l3.8-5.9h1.2V54.1z M54.1,54.1h2.7V50l-0.1,0.2L54.1,54.1z"
/>
<path
className="dashboarding-graphic--single-stat single-stat-a"
d="M61.5,52.2h0.8c0.5,0,0.9-0.1,1.2-0.4s0.4-0.6,0.4-1.1c0-1-0.5-1.5-1.5-1.5c-0.5,0-0.8,0.1-1.1,0.4
S61,50.3,61,50.7h-1.1c0-0.7,0.2-1.2,0.7-1.7s1.1-0.7,1.9-0.7c0.8,0,1.4,0.2,1.9,0.6s0.7,1,0.7,1.8c0,0.4-0.1,0.7-0.4,1.1
s-0.6,0.6-1,0.8c0.5,0.1,0.8,0.4,1.1,0.7s0.4,0.8,0.4,1.3c0,0.8-0.2,1.4-0.8,1.8s-1.2,0.7-2,0.7s-1.5-0.2-2-0.7s-0.8-1-0.8-1.7
h1.1c0,0.4,0.1,0.8,0.4,1.1s0.7,0.4,1.2,0.4c0.5,0,0.9-0.1,1.2-0.4s0.4-0.7,0.4-1.2c0-0.5-0.2-0.9-0.5-1.1s-0.7-0.4-1.3-0.4h-0.8
V52.2z"
/>
<path
className="dashboarding-graphic--single-stat single-stat-a"
d="M70.9,53.2c-0.2,0.3-0.5,0.5-0.8,0.7s-0.7,0.2-1,0.2c-0.5,0-0.9-0.1-1.3-0.4s-0.6-0.6-0.8-1s-0.3-0.9-0.3-1.5
c0-0.6,0.1-1.1,0.3-1.5s0.5-0.8,0.9-1s0.9-0.4,1.4-0.4c0.8,0,1.5,0.3,2,0.9s0.7,1.5,0.7,2.6v0.3c0,1.7-0.3,2.9-1,3.6
S69.3,57,68,57h-0.2v-0.9H68c0.9,0,1.6-0.2,2.1-0.7S70.8,54.2,70.9,53.2z M69.2,53.2c0.4,0,0.7-0.1,1-0.3s0.5-0.5,0.7-0.8v-0.4
c0-0.7-0.2-1.3-0.5-1.7s-0.7-0.7-1.2-0.7c-0.5,0-0.9,0.2-1.1,0.5s-0.4,0.8-0.4,1.4c0,0.6,0.1,1.1,0.4,1.4S68.7,53.2,69.2,53.2z"
/>
<path
className="dashboarding-graphic--single-stat single-stat-a"
d="M79,57h-5.6v-0.8l3-3.3c0.4-0.5,0.7-0.9,0.9-1.2s0.2-0.6,0.2-1c0-0.4-0.1-0.8-0.4-1.1s-0.6-0.4-1.1-0.4
c-0.5,0-1,0.2-1.3,0.5s-0.4,0.7-0.4,1.3h-1.1c0-0.8,0.3-1.4,0.8-1.9s1.2-0.7,2-0.7c0.8,0,1.4,0.2,1.9,0.6s0.7,1,0.7,1.7
c0,0.8-0.5,1.8-1.6,3l-2.3,2.5H79V57z"
/>
</g>
<g id="Line_Graph_A">
<polyline
className="dashboarding-graphic--line line-a"
points="40,121.8 54.4,121.8 68.8,115.5 83.1,118.6 97.5,109.8 111.9,114.2 126.2,109.8 140.6,109.8
155,117.2 "
/>
<polyline
className="dashboarding-graphic--line line-b"
points="40,118.2 54.4,118.2 68.8,104.8 83.1,74.5 97.5,99.5 111.9,92 126.2,103.8 140.6,105.5 155,109.8
"
/>
<polyline
className="dashboarding-graphic--axes"
points="40,74.5 40,130 155,130 "
/>
</g>
<g id="Single_Stat_99.5">
<path
className="dashboarding-graphic--single-stat single-stat-b"
d="M245.1,53.2c-0.2,0.3-0.5,0.5-0.8,0.7s-0.7,0.2-1,0.2c-0.5,0-0.9-0.1-1.3-0.4s-0.6-0.6-0.8-1
s-0.3-0.9-0.3-1.5c0-0.6,0.1-1.1,0.3-1.5s0.5-0.8,0.9-1s0.9-0.4,1.4-0.4c0.8,0,1.5,0.3,2,0.9s0.7,1.5,0.7,2.6v0.3
c0,1.7-0.3,2.9-1,3.6s-1.6,1.2-3,1.2H242v-0.9h0.2c0.9,0,1.6-0.2,2.1-0.7S245,54.2,245.1,53.2z M243.4,53.2c0.4,0,0.7-0.1,1-0.3
s0.5-0.5,0.7-0.8v-0.4c0-0.7-0.2-1.3-0.5-1.7s-0.7-0.7-1.2-0.7c-0.5,0-0.9,0.2-1.1,0.5s-0.4,0.8-0.4,1.4c0,0.6,0.1,1.1,0.4,1.4
S242.9,53.2,243.4,53.2z"
/>
<path
className="dashboarding-graphic--single-stat single-stat-b"
d="M251.8,53.2c-0.2,0.3-0.5,0.5-0.8,0.7s-0.7,0.2-1,0.2c-0.5,0-0.9-0.1-1.3-0.4s-0.6-0.6-0.8-1
s-0.3-0.9-0.3-1.5c0-0.6,0.1-1.1,0.3-1.5s0.5-0.8,0.9-1s0.9-0.4,1.4-0.4c0.8,0,1.5,0.3,2,0.9s0.7,1.5,0.7,2.6v0.3
c0,1.7-0.3,2.9-1,3.6s-1.6,1.2-3,1.2h-0.2v-0.9h0.2c0.9,0,1.6-0.2,2.1-0.7S251.7,54.2,251.8,53.2z M250.1,53.2
c0.4,0,0.7-0.1,1-0.3s0.5-0.5,0.7-0.8v-0.4c0-0.7-0.2-1.3-0.5-1.7s-0.7-0.7-1.2-0.7c-0.5,0-0.9,0.2-1.1,0.5s-0.4,0.8-0.4,1.4
c0,0.6,0.1,1.1,0.4,1.4S249.7,53.2,250.1,53.2z"
/>
<path
className="dashboarding-graphic--single-stat single-stat-b"
d="M254.5,56.4c0-0.2,0.1-0.3,0.2-0.5s0.3-0.2,0.5-0.2s0.4,0.1,0.5,0.2s0.2,0.3,0.2,0.5c0,0.2-0.1,0.3-0.2,0.5
s-0.3,0.2-0.5,0.2s-0.4-0.1-0.5-0.2S254.5,56.6,254.5,56.4z"
/>
<path
className="dashboarding-graphic--single-stat single-stat-b"
d="M258,52.7l0.4-4.3h4.4v1h-3.5l-0.3,2.3c0.4-0.2,0.9-0.4,1.4-0.4c0.8,0,1.4,0.3,1.9,0.8s0.7,1.2,0.7,2.1
c0,0.9-0.2,1.6-0.7,2.1s-1.1,0.8-2,0.8c-0.8,0-1.4-0.2-1.8-0.6s-0.7-1-0.8-1.7h1c0.1,0.5,0.2,0.9,0.5,1.1s0.7,0.4,1.1,0.4
c0.5,0,0.9-0.2,1.2-0.5s0.4-0.8,0.4-1.4c0-0.6-0.2-1-0.5-1.4s-0.7-0.5-1.2-0.5c-0.5,0-0.8,0.1-1.1,0.3l-0.3,0.2L258,52.7z"
/>
<path
className="dashboarding-graphic--single-stat single-stat-b"
d="M264.2,50.1c0-0.5,0.2-0.9,0.5-1.3s0.7-0.5,1.3-0.5c0.5,0,0.9,0.2,1.3,0.5s0.5,0.8,0.5,1.3v0.4
c0,0.5-0.2,0.9-0.5,1.3s-0.7,0.5-1.2,0.5c-0.5,0-0.9-0.2-1.3-0.5s-0.5-0.8-0.5-1.3V50.1z M265,50.6c0,0.3,0.1,0.6,0.3,0.8
s0.4,0.3,0.7,0.3c0.3,0,0.5-0.1,0.7-0.3s0.3-0.5,0.3-0.8v-0.4c0-0.3-0.1-0.6-0.3-0.8c-0.2-0.2-0.4-0.3-0.7-0.3s-0.5,0.1-0.7,0.3
c-0.2,0.2-0.3,0.5-0.3,0.8V50.6z M266.2,56.4l-0.6-0.4l4.2-6.7l0.6,0.4L266.2,56.4z M268.3,54.9c0-0.5,0.2-0.9,0.5-1.3
s0.7-0.5,1.3-0.5s0.9,0.2,1.3,0.5s0.5,0.8,0.5,1.3v0.4c0,0.5-0.2,0.9-0.5,1.3s-0.7,0.5-1.3,0.5s-0.9-0.2-1.3-0.5s-0.5-0.8-0.5-1.3
V54.9z M269.1,55.4c0,0.3,0.1,0.6,0.3,0.8s0.4,0.3,0.7,0.3c0.3,0,0.5-0.1,0.7-0.3s0.3-0.5,0.3-0.8v-0.4c0-0.3-0.1-0.6-0.3-0.8
c-0.2-0.2-0.4-0.3-0.7-0.3c-0.3,0-0.5,0.1-0.7,0.3c-0.2,0.2-0.3,0.5-0.3,0.8V55.4z"
/>
</g>
<g id="Table">
<rect
x="167"
y="74.5"
className="dashboarding-graphic--axes"
width="115"
height="55.5"
/>
<rect
x="167"
y="74.5"
className="dashboarding-graphic--axes"
width="23"
height="55.5"
/>
<rect
x="190"
y="74.5"
className="dashboarding-graphic--axes"
width="23"
height="55.5"
/>
<rect
x="213"
y="74.5"
className="dashboarding-graphic--axes"
width="23"
height="55.5"
/>
<rect
x="236"
y="74.5"
className="dashboarding-graphic--axes"
width="23"
height="55.5"
/>
<rect
x="259"
y="74.5"
className="dashboarding-graphic--axes"
width="23"
height="55.5"
/>
<rect
x="167"
y="74.5"
className="dashboarding-graphic--axes"
width="115"
height="7.9"
/>
<rect
x="167"
y="82.4"
className="dashboarding-graphic--axes"
width="115"
height="7.9"
/>
<rect
x="167"
y="90.4"
className="dashboarding-graphic--axes"
width="115"
height="7.9"
/>
<rect
x="167"
y="98.3"
className="dashboarding-graphic--axes"
width="115"
height="7.9"
/>
<rect
x="167"
y="106.2"
className="dashboarding-graphic--axes"
width="115"
height="7.9"
/>
<rect
x="167"
y="114.1"
className="dashboarding-graphic--axes"
width="115"
height="7.9"
/>
<rect
x="167"
y="122.1"
className="dashboarding-graphic--axes"
width="115"
height="7.9"
/>
<rect
id="Highlight_Blue"
x="190"
y="82.4"
className="dashboarding-graphic--axes table-a"
width="23"
height="7.9"
/>
<rect
id="Highlight_Purple"
x="236"
y="106.2"
className="dashboarding-graphic--axes table-b"
width="23"
height="7.9"
/>
</g>
<g id="Line_Graph_B">
<polyline
className="dashboarding-graphic--line line-c"
points="39.5,167.8 48.8,167.8 58.1,157 67.4,162.4 76.8,147 86.1,154.8 95.4,147 104.7,147 114,160 "
/>
<polyline
className="dashboarding-graphic--axes"
points="39.5,142 39.5,181.5 114,181.5 "
/>
</g>
<g id="Bar_Chart_1_">
<polyline
className="dashboarding-graphic--axes"
points="126,142 126,181.5 196,181.5 "
/>
<g id="Bar_Chart">
<rect
x="130"
y="166.3"
className="dashboarding-graphic--bar bar-a"
width="6.3"
height="15.2"
transform="scale(1,-1) translate(0,-347.2)"
/>
<rect
x="139.1"
y="149.3"
className="dashboarding-graphic--bar bar-b"
width="6.3"
height="32.2"
transform="scale(1,-1) translate(0,-330)"
/>
<rect
x="148.3"
y="155.7"
className="dashboarding-graphic--bar bar-c"
width="6.3"
height="25.8"
transform="scale(1,-1) translate(0,-336.5)"
/>
<rect
x="157.4"
y="177.5"
className="dashboarding-graphic--bar bar-d"
width="6.3"
height="4"
transform="scale(1,-1) translate(0,-358.5)"
/>
<rect
x="166.5"
y="179.5"
className="dashboarding-graphic--bar bar-e"
width="6.3"
height="2"
transform="scale(1,-1) translate(0,-360.5)"
/>
<rect
x="175.6"
y="173.9"
className="dashboarding-graphic--bar bar-f"
width="6.3"
height="7.6"
transform="scale(1,-1) translate(0,-355)"
/>
<rect
x="184.7"
y="177.5"
className="dashboarding-graphic--bar bar-g"
width="6.3"
height="4"
transform="scale(1,-1) translate(0,-358.5)"
/>
</g>
</g>
<g id="Line_Graph_C">
<polyline
className="dashboarding-graphic--line line-d"
points="207.7,161 217.1,173.9 226.4,176.2 235.7,163.7 245,167.8 254.3,152.9 264.1,145 272.9,148
282.3,148 "
/>
<polyline
className="dashboarding-graphic--axes"
points="207.7,142 207.7,181.5 282.3,181.5 "
/>
</g>
<g id="Single_Stat_4">
<path
className="dashboarding-graphic--single-stat single-stat-c"
d="M194.7,53.9h1.2v0.9h-1.2v2h-1.1v-2h-3.9v-0.6l3.8-5.9h1.2V53.9z M190.9,53.9h2.7v-4.2l-0.1,0.2L190.9,53.9z
"
/>
</g>
<g id="Single_Stat_ABC">
<path
className="dashboarding-graphic--single-stat single-stat-d"
d="M123.4,54.8h-3.6L119,57h-1.2l3.3-8.5h1l3.3,8.5h-1.2L123.4,54.8z M120.2,53.8h2.9l-1.5-4L120.2,53.8z"
/>
<path
className="dashboarding-graphic--single-stat single-stat-d"
d="M126.5,57v-8.5h2.8c0.9,0,1.6,0.2,2.1,0.6s0.7,0.9,0.7,1.7c0,0.4-0.1,0.8-0.3,1.1s-0.5,0.5-0.9,0.7
c0.5,0.1,0.8,0.4,1.1,0.7s0.4,0.8,0.4,1.3c0,0.8-0.2,1.4-0.7,1.8s-1.2,0.7-2.1,0.7H126.5z M127.6,52.1h1.7c0.5,0,0.9-0.1,1.2-0.4
c0.3-0.2,0.4-0.6,0.4-1c0-0.5-0.1-0.8-0.4-1c-0.3-0.2-0.7-0.3-1.2-0.3h-1.7V52.1z M127.6,53v3.1h1.9c0.5,0,0.9-0.1,1.2-0.4
s0.5-0.6,0.5-1.1c0-1-0.6-1.5-1.7-1.5H127.6z"
/>
<path
className="dashboarding-graphic--single-stat single-stat-d"
d="M140.3,54.3c-0.1,0.9-0.4,1.6-1,2.1s-1.3,0.7-2.2,0.7c-1,0-1.8-0.4-2.4-1.1s-0.9-1.7-0.9-2.9v-0.8
c0-0.8,0.1-1.5,0.4-2.1s0.7-1.1,1.2-1.4s1.1-0.5,1.8-0.5c0.9,0,1.6,0.3,2.2,0.8s0.9,1.2,1,2.1h-1.1c-0.1-0.7-0.3-1.2-0.6-1.5
s-0.8-0.5-1.4-0.5c-0.7,0-1.3,0.3-1.7,0.8c-0.4,0.5-0.6,1.3-0.6,2.3v0.8c0,0.9,0.2,1.7,0.6,2.2s0.9,0.8,1.6,0.8
c0.6,0,1.1-0.1,1.4-0.4s0.6-0.8,0.7-1.5H140.3z"
/>
</g>
</g>
</svg>
</div>
)
}
}

View File

@ -0,0 +1,90 @@
/*
Getting Started Explore Graphic
------------------------------------------------------------------------------
*/
@import 'src/style/modules';
$explore-graphic-anim-time: 0.5s;
.explore-graphic--bg {
transition: fill 0.25s ease;
fill: $g2-kevlar;
}
.explore-graphic--hex {
fill: $g5-pepper;
transition: fill 0.25s ease;
}
.hex-a,
.hex-b {
transition: fill $explore-graphic-anim-time ease;
}
.explore-graphic--text {
stroke: transparent;
fill: none;
transition: stroke $explore-graphic-anim-time ease;
}
.explore-graphic--diagnostic {
fill: transparent;
transition: fill $explore-graphic-anim-time ease;
}
.explore-graphic--flows {
opacity: 0;
transition: opacity $explore-graphic-anim-time ease;
}
@keyframes flow {
0% {
stroke-dashoffset: 0px;
}
100% {
stroke-dashoffset: 8px;
}
}
.explore-graphic--flow {
fill: none;
stroke-width: 2px;
stroke-dasharray: 4px;
stroke-dashoffset: 0px;
animation: flow 1s linear infinite;
}
.flow-a {
stroke: $c-honeydew;
}
.flow-b {
stroke: $c-pool;
animation-direction: reverse;
}
/* Hover State */
.getting-started--card:hover {
.explore-graphic--bg {
fill: $g3-castle;
}
.explore-graphic--hex {
fill: $g6-smoke;
}
.explore-graphic--text {
stroke: $g13-mist;
}
.hex-a,
.diagnostic-a {
fill: $c-pool;
}
.hex-b,
.diagnostic-b {
fill: $c-honeydew;
}
.explore-graphic--flows {
opacity: 1;
}
}

View File

@ -0,0 +1,231 @@
// Libraries
import React, {PureComponent} from 'react'
// Styles
import 'src/me/graphics/ExploreGraphic.scss'
export default class GettingStarted extends PureComponent {
public render() {
return (
<div className="getting-started--image explore-graphic">
<svg
version="1.1"
x="0px"
y="0px"
width="322px"
height="225px"
viewBox="0 0 322 225"
>
<g>
<path
id="Background_2_"
className="explore-graphic--bg"
d="M302,34.3l-8.8-15.2c-2-3.4-6-5.7-9.9-5.7h-17.6c-4,0-7.9,2.3-9.9,5.7l-7.5,13h-12.4
l-7.5-13c-2-3.4-6-5.7-9.9-5.7h-17.6c-4,0-7.9,2.3-9.9,5.7l-7.5,13h-15c-4,0-7.9,2.3-9.9,5.7l-7.5,13h-12.4l-7.5-13
c-2-3.4-6-5.7-9.9-5.7h-15l-7.5-13c-2-3.4-6-5.7-9.9-5.7H71.2c-4,0-7.9,2.3-9.9,5.7l-7.5,13h-15c-4,0-7.9,2.3-9.9,5.7L20,53
c-2,3.4-2,8,0,11.5l7.5,13l-7.5,13c-2,3.4-2,8,0,11.5l7.5,13l-7.5,13c-2,3.4-2,8,0,11.5l8.8,15.2c2,3.4,6,5.7,9.9,5.7h15l7.5,13
c2,3.4,6,5.7,9.9,5.7h17.6c4,0,7.9-2.3,9.9-5.7l7.5-13h12.4l6.2,10.7l-7.5,13c-2,3.4-2,8,0,11.5l8.8,15.2c2,3.4,6,5.7,9.9,5.7h17.6
c4,0,7.9-2.3,9.9-5.7l7.5-13h12.4l7.5,13c2,3.4,6,5.7,9.9,5.7h17.6c4,0,7.9-2.3,9.9-5.7l8.8-15.2c2-3.4,2-8,0-11.5l-7.5-13
l6.2-10.7h15c4,0,7.9-2.3,9.9-5.7l8.8-15.2c2-3.4,2-8,0-11.5l-7.5-13l7.5-13c2-3.4,2-8,0-11.5l-7.5-13l6.2-10.7h15
c4,0,7.9-2.3,9.9-5.7l8.8-15.2C303.9,42.4,303.9,37.7,302,34.3z"
/>
<g id="Hexes">
<path
className="explore-graphic--hex"
d="M200.8,59.3h17.6c0.9,0,2.1,0.7,2.6,1.5l8.8,15.2c0.5,0.8,0.5,2.2,0,3L221,94.2c-0.5,0.8-1.6,1.5-2.6,1.5
h-17.6c-0.9,0-2.1-0.7-2.6-1.5L189.4,79c-0.5-0.8-0.5-2.2,0-3l8.8-15.2C198.7,59.9,199.9,59.3,200.8,59.3z"
/>
<path
className="explore-graphic--hex"
d="M157,57.3l8.8-15.2c0.5-0.8,1.6-1.5,2.6-1.5H186c0.9,0,2.1,0.7,2.6,1.5l8.8,15.2c0.5,0.8,0.5,2.2,0,3
l-8.8,15.2c-0.5,0.8-1.6,1.5-2.6,1.5h-17.6c-0.9,0-2.1-0.7-2.6-1.5L157,60.2C156.6,59.4,156.6,58.1,157,57.3z"
/>
<path
className="explore-graphic--hex"
d="M103.6,40.5h17.6c0.9,0,2.1,0.7,2.6,1.5v0l8.8,15.2c0.5,0.8,0.5,2.2,0,3l-8.8,15.2c-0.5,0.8-1.6,1.5-2.6,1.5
h-17.6c-0.9,0-2.1-0.7-2.6-1.5l-8.8-15.2c-0.5-0.8-0.5-2.2,0-3L101,42C101.5,41.2,102.7,40.5,103.6,40.5z"
/>
<path
className="explore-graphic--hex"
d="M68.6,56.8l-8.8-15.2c-0.5-0.8-0.5-2.2,0-3l8.8-15.2c0.5-0.8,1.6-1.5,2.6-1.5h17.6c0.9,0,2.1,0.7,2.6,1.5
l8.8,15.2c0.5,0.8,0.5,2.2,0,3l-8.8,15.2c-0.5,0.8-1.6,1.5-2.6,1.5H71.2C70.3,58.3,69.1,57.6,68.6,56.8z"
/>
<path
className="explore-graphic--hex"
d="M67.7,97.7l-8.8,15.2c-0.5,0.8-1.6,1.5-2.6,1.5H38.8c-0.9,0-2.1-0.7-2.6-1.5l-8.8-15.2c-0.5-0.8-0.5-2.2,0-3
l8.8-15.2c0.5-0.8,1.6-1.5,2.6-1.5h17.6c0.9,0,2.1,0.7,2.6,1.5l8.8,15.2C68.2,95.5,68.2,96.9,67.7,97.7z"
/>
<path
className="explore-graphic--hex"
d="M88.8,95.7H71.2c-0.9,0-2.1-0.7-2.6-1.5L59.8,79c-0.5-0.8-0.5-2.2,0-3l8.8-15.2c0.5-0.8,1.6-1.5,2.6-1.5h17.6
c0.9,0,2.1,0.7,2.6,1.5l8.8,15.2c0.5,0.8,0.5,2.2,0,3l-8.8,15.2C90.9,95,89.7,95.7,88.8,95.7z"
/>
<path
className="explore-graphic--hex"
d="M132.6,97.7l-8.8,15.2c-0.5,0.8-1.6,1.5-2.6,1.5h-17.6c-0.9,0-2.1-0.7-2.6-1.5l-8.8-15.2
c-0.5-0.8-0.5-2.2,0-3l8.8-15.2c0.5-0.8,1.6-1.5,2.6-1.5h17.6c0.9,0,2.1,0.7,2.6,1.5v0l8.8,15.2C133,95.5,133,96.9,132.6,97.7z"
/>
<path
className="explore-graphic--hex"
d="M153.6,95.7H136c-0.9,0-2.1-0.7-2.6-1.5L124.6,79c-0.5-0.8-0.5-2.2,0-3l8.8-15.2c0.5-0.8,1.6-1.5,2.6-1.5
h17.6c0.9,0,2.1,0.7,2.6,1.5L165,76c0.5,0.8,0.5,2.2,0,3l-8.8,15.2C155.7,95,154.5,95.7,153.6,95.7z"
/>
<path
className="explore-graphic--hex"
d="M133.4,169l-8.8-15.2c-0.5-0.8-0.5-2.2,0-3l8.8-15.2c0.5-0.8,1.6-1.5,2.6-1.5h17.6c0.9,0,2.1,0.7,2.6,1.5
l8.8,15.2c0.5,0.8,0.5,2.2,0,3l-8.8,15.2c-0.5,0.8-1.6,1.5-2.6,1.5H136C135.1,170.5,133.9,169.8,133.4,169z"
/>
<path
className="explore-graphic--hex"
d="M165,191.2l-8.8,15.2c-0.5,0.8-1.6,1.5-2.6,1.5H136c-0.9,0-2.1-0.7-2.6-1.5l-8.8-15.2c-0.5-0.8-0.5-2.2,0-3
l8.8-15.2c0.5-0.8,1.6-1.5,2.6-1.5h17.6c0.9,0,2.1,0.7,2.6,1.5l8.8,15.2C165.4,189.1,165.4,190.4,165,191.2z"
/>
<path
className="explore-graphic--hex"
d="M200.8,171.5h17.6c0.9,0,2.1,0.7,2.6,1.5l8.8,15.2c0.5,0.8,0.5,2.2,0,3l-8.8,15.2c-0.5,0.8-1.6,1.5-2.6,1.5
h-17.6c-0.9,0-2.1-0.7-2.6-1.5l-8.8-15.2c-0.5-0.8-0.5-2.2,0-3l8.8-15.2C198.7,172.2,199.9,171.5,200.8,171.5z"
/>
<path
className="explore-graphic--hex"
d="M188.6,154.3l8.8,15.2c0.5,0.8,0.5,2.2,0,3l-8.8,15.2c-0.5,0.8-1.6,1.5-2.6,1.5h-17.6c-0.9,0-2.1-0.7-2.6-1.5
l-8.8-15.2c-0.5-0.8-0.5-2.2,0-3l8.8-15.2c0.5-0.8,1.6-1.5,2.6-1.5H186C186.9,152.8,188.1,153.5,188.6,154.3z"
/>
<path
className="explore-graphic--hex"
d="M197.4,135.1l-8.8,15.2c-0.5,0.8-1.6,1.5-2.6,1.5h-17.6c-0.9,0-2.1-0.7-2.6-1.5l-8.8-15.2
c-0.5-0.8-0.5-2.2,0-3l8.8-15.2c0.5-0.8,1.6-1.5,2.6-1.5H186c0.9,0,2.1,0.7,2.6,1.5l8.8,15.2C197.8,132.9,197.8,134.3,197.4,135.1
z"
/>
<path
className="explore-graphic--hex"
d="M229.8,116.4l-8.8,15.2c-0.5,0.8-1.6,1.5-2.6,1.5h-17.6c-0.9,0-2.1-0.7-2.6-1.5l-8.8-15.2
c-0.5-0.8-0.5-2.2,0-3l8.8-15.2c0.5-0.8,1.6-1.5,2.6-1.5h17.6c0.9,0,2.1,0.7,2.6,1.5l8.8,15.2
C230.2,114.2,230.2,115.6,229.8,116.4z"
/>
<path
className="explore-graphic--hex"
d="M253.4,79.5l8.8,15.2c0.5,0.8,0.5,2.2,0,3l-8.8,15.2c-0.5,0.8-1.6,1.5-2.6,1.5h-17.6c-0.9,0-2.1-0.7-2.6-1.5
l-8.8-15.2c-0.5-0.8-0.5-2.2,0-3l8.8-15.2c0.5-0.8,1.6-1.5,2.6-1.5h17.6C251.7,78,252.9,78.6,253.4,79.5z"
/>
<path
className="explore-graphic--hex"
d="M262.2,60.2l-8.8,15.2c-0.5,0.8-1.6,1.5-2.6,1.5h-17.6c-0.9,0-2.1-0.7-2.6-1.5l-8.8-15.2
c-0.5-0.8-0.5-2.2,0-3l8.8-15.2c0.5-0.8,1.6-1.5,2.6-1.5h17.6c0.9,0,2.1,0.7,2.6,1.5l8.8,15.2C262.7,58.1,262.7,59.4,262.2,60.2z"
/>
<path
className="explore-graphic--hex"
d="M294.6,41.5l-8.8,15.2c-0.5,0.8-1.6,1.5-2.6,1.5h-17.6c-0.9,0-2.1-0.7-2.6-1.5l-8.8-15.2
c-0.5-0.8-0.5-2.2,0-3l8.8-15.2c0.5-0.8,1.6-1.5,2.6-1.5h17.6c0.9,0,2.1,0.7,2.6,1.5l8.8,15.2C295.1,39.4,295.1,40.7,294.6,41.5z"
/>
</g>
<path
id="DiagnosticB"
className="explore-graphic--diagnostic diagnostic-b"
d="M178.4,10.4c0-1-0.8-1.9-1.9-1.9s-1.9,0.8-1.9,1.9c0,0.9,0.6,1.6,1.4,1.8v73.6
c0,0.3,0.2,0.5,0.5,0.5s0.5-0.2,0.5-0.5V12.2C177.8,12,178.4,11.3,178.4,10.4z"
/>
<path
id="DiagnosticA"
className="explore-graphic--diagnostic diagnostic-a"
d="M48.1,206.3v-59.9c0-0.3-0.2-0.5-0.5-0.5s-0.5,0.2-0.5,0.5v59.9c-0.8,0.2-1.4,0.9-1.4,1.8
c0,1,0.8,1.9,1.9,1.9s1.9-0.8,1.9-1.9C49.5,207.2,48.9,206.5,48.1,206.3z"
/>
<g className="explore-graphic--flows">
<path
id="FlowB"
className="explore-graphic--flow flow-b"
d="M47.6,134.9l30,16.5c1,0.5,2.2,0.5,3.2,0l30-16.9c1-0.6,2.2-0.6,3.2,0l29.1,16.8
c1,0.6,2.3,0.6,3.3,0l29.1-16.8c1-0.6,1.6-1.7,1.6-2.8V95.2"
/>
<path
id="FlowA"
className="explore-graphic--flow flow-a"
d="M47.6,134.9l30.1-19c0.9-0.6,1.5-1.6,1.5-2.8V79.4c0-1.2,0.6-2.3,1.7-2.9l29.9-16.9
c1-0.6,2.2-0.6,3.2,0l30.9,17.8l32.3,17.8"
/>
</g>
<g id="Text">
<line
className="explore-graphic--text"
x1="52.6"
y1="203.1"
x2="75.2"
y2="203.1"
/>
<line
className="explore-graphic--text"
x1="52.6"
y1="199.1"
x2="79.2"
y2="199.1"
/>
<line
className="explore-graphic--text"
x1="52.6"
y1="195.1"
x2="83.5"
y2="195.1"
/>
<line
className="explore-graphic--text"
x1="52.6"
y1="191.1"
x2="69.4"
y2="191.1"
/>
<line
className="explore-graphic--text"
x1="171.5"
y1="15.4"
x2="148.9"
y2="15.4"
/>
<line
className="explore-graphic--text"
x1="171.5"
y1="19.4"
x2="155.3"
y2="19.4"
/>
<line
className="explore-graphic--text"
x1="171.5"
y1="23.4"
x2="146.3"
y2="23.4"
/>
<line
className="explore-graphic--text"
x1="171.5"
y1="27.4"
x2="159.3"
y2="27.4"
/>
<line
className="explore-graphic--text"
x1="155.3"
y1="27.4"
x2="149.2"
y2="27.4"
/>
</g>
<path
id="HighlightA"
className="explore-graphic--hex hex-a"
d="M58.9,116.9l8.8,15.2c0.5,0.8,0.5,2.2,0,3l-8.8,15.2c-0.5,0.8-1.6,1.5-2.6,1.5H38.8
c-0.9,0-2.1-0.7-2.6-1.5l-8.8-15.2c-0.5-0.8-0.5-2.2,0-3l8.8-15.2c0.5-0.8,1.6-1.5,2.6-1.5h17.6C57.3,115.4,58.5,116.1,58.9,116.9z
"
/>
<path
id="HighlightB"
className="explore-graphic--hex hex-b"
d="M165.8,112.9L157,97.7c-0.5-0.8-0.5-2.2,0-3l8.8-15.2c0.5-0.8,1.6-1.5,2.6-1.5H186
c0.9,0,2.1,0.7,2.6,1.5l8.8,15.2c0.5,0.8,0.5,2.2,0,3l-8.8,15.2c-0.5,0.8-1.6,1.5-2.6,1.5h-17.6
C167.5,114.4,166.3,113.7,165.8,112.9z"
/>
</g>
</svg>
</div>
)
}
}

View File

@ -88,6 +88,7 @@ export class LineProtocol extends PureComponent<Props> {
tabs={this.LineProtocolTabs}
bucket={bucket}
org={org}
handleSubmit={this.handleSubmit}
/>
)
}

View File

@ -31,6 +31,7 @@ interface OwnProps {
tabs: LineProtocolTab[]
bucket: string
org: string
handleSubmit?: () => void
}
type Props = OwnProps & DispatchProps & StateProps
@ -69,6 +70,7 @@ export class LineProtocolTabs extends PureComponent<Props, State> {
tabs,
setLineProtocolBody,
lineProtocolBody,
handleSubmit,
} = this.props
const {urlInput} = this.state
@ -83,7 +85,13 @@ export class LineProtocolTabs extends PureComponent<Props, State> {
<Grid>
<Grid.Row>
<Grid.Column widthXS={Columns.Twelve}>
<Grid.Column
widthXS={Columns.Twelve}
widthMD={Columns.Ten}
offsetMD={Columns.One}
widthLG={Columns.Eight}
offsetLG={Columns.Two}
>
<div className="onboarding--admin-user-form">
<div className={'wizard-step--lp-body'}>
<TabBody
@ -93,6 +101,7 @@ export class LineProtocolTabs extends PureComponent<Props, State> {
urlInput={urlInput}
lineProtocolBody={lineProtocolBody}
setLineProtocolBody={setLineProtocolBody}
handleSubmit={handleSubmit}
/>
</div>

View File

@ -21,23 +21,19 @@ interface Props {
setLineProtocolBody: typeof setLineProtocolBody
onURLChange: (url: string) => void
urlInput: string
handleSubmit?: () => void
}
export default class extends PureComponent<Props> {
public render() {
const {
setLineProtocolBody,
lineProtocolBody,
activeLPTab,
urlInput,
} = this.props
const {lineProtocolBody, activeLPTab, urlInput} = this.props
switch (activeLPTab) {
case LineProtocolTab.UploadFile:
return (
<DragAndDrop
submitText="Upload File"
handleSubmit={setLineProtocolBody}
handleSubmit={this.handleSetLineProtocol}
submitOnDrop={true}
submitOnUpload={true}
/>
@ -47,7 +43,7 @@ export default class extends PureComponent<Props> {
<TextArea
value={lineProtocolBody}
placeholder="Write text here"
onChange={setLineProtocolBody}
onChange={this.handleSetLineProtocol}
/>
)
case LineProtocolTab.EnterURL:
@ -81,4 +77,12 @@ export default class extends PureComponent<Props> {
const {value} = e.target
this.props.onURLChange(value)
}
private handleSetLineProtocol = async (lpBody: string) => {
const {setLineProtocolBody, handleSubmit} = this.props
await setLineProtocolBody(lpBody)
if (handleSubmit) {
handleSubmit()
}
}
}

View File

@ -29,6 +29,7 @@ exports[`LineProtocol rendering renders! 1`] = `
</h5>
<Connect(LineProtocolTabs)
bucket="a"
handleSubmit={[Function]}
org="a"
tabs={
Array [

View File

@ -15,6 +15,10 @@ exports[`LineProtocolTabs rendering renders! 1`] = `
<Grid>
<GridRow>
<GridColumn
offsetLG={2}
offsetMD={1}
widthLG={8}
widthMD={10}
widthXS={12}
>
<div

View File

@ -2,10 +2,13 @@ import React, {SFC} from 'react'
interface Props {
title: string
altText?: string
}
const PageTitle: SFC<Props> = ({title}) => (
<h1 className="page--title">{title}</h1>
const PageTitle: SFC<Props> = ({title, altText}) => (
<h1 className="page--title" title={altText}>
{title}
</h1>
)
export default PageTitle

View File

@ -65,7 +65,7 @@
align-items: center;
flex-wrap: nowrap;
margin-top: 18px;
> button.btn {
> button {
margin: 0 4px;
}
}
@ -109,7 +109,7 @@
right: 20px;
flex-direction: column;
align-items: stretch;
> button.btn {
> button {
margin: 2px 0;
}
}

View File

@ -1,5 +1,6 @@
import React, {PureComponent} from 'react'
import classnames from 'classnames'
import {Button, ComponentColor, ComponentSize, ButtonType} from 'src/clockface'
import './DragAndDrop.scss'
@ -133,29 +134,33 @@ class DragAndDrop extends PureComponent<Props, State> {
if (submitOnDrop) {
return (
<span className="drag-and-drop--buttons">
<button
className="btn btn-sm btn-default"
<Button
color={ComponentColor.Default}
text="Cancel"
size={ComponentSize.Medium}
type={ButtonType.Button}
onClick={this.handleCancelFile}
type="button"
>
Cancel
</button>
/>
</span>
)
}
return (
<span className="drag-and-drop--buttons">
<button className="btn btn-sm btn-success" onClick={this.handleSubmit}>
{submitText}
</button>
<button
className="btn btn-sm btn-default"
<Button
color={ComponentColor.Primary}
text={submitText}
size={ComponentSize.Medium}
type={ButtonType.Submit}
onClick={this.handleSubmit}
/>
<Button
color={ComponentColor.Default}
text="Cancel"
size={ComponentSize.Medium}
type={ButtonType.Submit}
onClick={this.handleCancelFile}
type="button"
>
Cancel
</button>
/>
</span>
)
}

View File

@ -32,6 +32,10 @@ export default class EmptyQueryView extends PureComponent<Props> {
fallbackNote,
} = this.props
if (loading === RemoteDataState.NotStarted) {
return <EmptyGraphMessage message={emptyGraphCopy} />
}
if (!queries.length) {
return <EmptyGraphMessage message={emptyGraphCopy} />
}

View File

@ -4,7 +4,6 @@ import {connect} from 'react-redux'
import {get} from 'lodash'
// Components
import TimeMachineControls from 'src/shared/components/TimeMachineControls'
import {DraggableResizer, Stack} from 'src/clockface'
import TimeMachineBottom from 'src/shared/components/TimeMachineBottom'
import TimeMachineVis from 'src/shared/components/TimeMachineVis'
@ -64,7 +63,6 @@ class TimeMachine extends Component<Props, State> {
>
<DraggableResizer.Panel>
<div className="time-machine--top">
<TimeMachineControls queriesState={queriesState} />
<TimeMachineVis queriesState={queriesState} />
</div>
</DraggableResizer.Panel>

View File

@ -1,95 +0,0 @@
// Libraries
import React, {PureComponent} from 'react'
import {connect} from 'react-redux'
// Components
import TimeRangeDropdown from 'src/shared/components/TimeRangeDropdown'
import CSVExportButton from 'src/shared/components/CSVExportButton'
import {
SlideToggle,
ComponentSize,
ComponentSpacer,
Alignment,
} from 'src/clockface'
import TimeMachineRefreshDropdown from 'src/shared/components/TimeMachineRefreshDropdown'
// Actions
import {
setTimeRange,
setIsViewingRawData,
} from 'src/shared/actions/v2/timeMachines'
// Utils
import {getActiveTimeMachine} from 'src/shared/selectors/timeMachines'
// Types
import {TimeRange, AppState} from 'src/types/v2'
import {QueriesState} from 'src/shared/components/TimeSeries'
interface StateProps {
timeRange: TimeRange
isViewingRawData: boolean
}
interface DispatchProps {
onSetTimeRange: (timeRange: TimeRange) => void
onSetIsViewingRawData: typeof setIsViewingRawData
}
interface OwnProps {
queriesState: QueriesState
}
type Props = StateProps & DispatchProps & OwnProps
class TimeMachineControls extends PureComponent<Props> {
public render() {
const {
timeRange,
onSetTimeRange,
isViewingRawData,
queriesState: {files},
} = this.props
return (
<div className="time-machine--controls">
<ComponentSpacer align={Alignment.Right}>
<SlideToggle.Label text="View Raw Data" />
<SlideToggle
active={isViewingRawData}
onChange={this.handleToggleIsViewingRawData}
size={ComponentSize.ExtraSmall}
/>
<CSVExportButton files={files} />
<TimeMachineRefreshDropdown />
<TimeRangeDropdown
timeRange={timeRange}
onSetTimeRange={onSetTimeRange}
/>
</ComponentSpacer>
</div>
)
}
private handleToggleIsViewingRawData = () => {
const {isViewingRawData, onSetIsViewingRawData} = this.props
onSetIsViewingRawData(!isViewingRawData)
}
}
const mstp = (state: AppState): StateProps => {
const {timeRange, isViewingRawData} = getActiveTimeMachine(state)
return {timeRange, isViewingRawData}
}
const mdtp: DispatchProps = {
onSetTimeRange: setTimeRange,
onSetIsViewingRawData: setIsViewingRawData,
}
export default connect<StateProps, DispatchProps, OwnProps>(
mstp,
mdtp
)(TimeMachineControls)

View File

@ -1,10 +1,13 @@
// Libraries
import React, {SFC} from 'react'
import React, {PureComponent} from 'react'
import {connect} from 'react-redux'
// Components
import TimeMachineFluxEditor from 'src/shared/components/TimeMachineFluxEditor'
import CSVExportButton from 'src/shared/components/CSVExportButton'
import TimeMachineQueriesSwitcher from 'src/shared/components/TimeMachineQueriesSwitcher'
import TimeMachineRefreshDropdown from 'src/shared/components/TimeMachineRefreshDropdown'
import TimeRangeDropdown from 'src/shared/components/TimeRangeDropdown'
import TimeMachineQueryTab from 'src/shared/components/TimeMachineQueryTab'
import TimeMachineQueryBuilder from 'src/shared/components/TimeMachineQueryBuilder'
import TimeMachineInfluxQLEditor from 'src/shared/components/TimeMachineInfluxQLEditor'
@ -12,14 +15,21 @@ import TimeMachineQueriesTimer from 'src/shared/components/TimeMachineQueriesTim
import SubmitQueryButton from 'src/shared/components/SubmitQueryButton'
import {
Button,
ComponentSpacer,
Alignment,
ComponentColor,
ComponentSize,
ButtonShape,
IconFont,
SlideToggle,
} from 'src/clockface'
// Actions
import {addQuery} from 'src/shared/actions/v2/timeMachines'
import {
setTimeRange,
setIsViewingRawData,
} from 'src/shared/actions/v2/timeMachines'
// Utils
import {
@ -36,6 +46,7 @@ import {
DashboardQuery,
InfluxLanguage,
QueryEditMode,
TimeRange,
} from 'src/types/v2'
import {DashboardDraftQuery} from 'src/types/v2/dashboards'
import {QueriesState} from 'src/shared/components/TimeSeries'
@ -43,10 +54,14 @@ import {QueriesState} from 'src/shared/components/TimeSeries'
interface StateProps {
activeQuery: DashboardQuery
draftQueries: DashboardDraftQuery[]
timeRange: TimeRange
isViewingRawData: boolean
}
interface DispatchProps {
onAddQuery: typeof addQuery
onSetTimeRange: typeof setTimeRange
onSetIsViewingRawData: typeof setIsViewingRawData
}
interface OwnProps {
@ -55,62 +70,98 @@ interface OwnProps {
type Props = StateProps & DispatchProps & OwnProps
const TimeMachineQueries: SFC<Props> = props => {
const {activeQuery, queriesState, draftQueries, onAddQuery} = props
class TimeMachineQueries extends PureComponent<Props> {
public render() {
const {
queriesState,
draftQueries,
onAddQuery,
timeRange,
onSetTimeRange,
isViewingRawData,
queriesState: {files},
} = this.props
let queryEditor
if (activeQuery.editMode === QueryEditMode.Builder) {
queryEditor = <TimeMachineQueryBuilder />
} else if (activeQuery.type === InfluxLanguage.Flux) {
queryEditor = <TimeMachineFluxEditor />
} else if (activeQuery.type === InfluxLanguage.InfluxQL) {
queryEditor = <TimeMachineInfluxQLEditor />
return (
<div className="time-machine-queries">
<div className="time-machine-queries--controls">
<div className="time-machine-queries--tabs">
{draftQueries.map((query, queryIndex) => (
<TimeMachineQueryTab
key={queryIndex}
queryIndex={queryIndex}
query={query}
/>
))}
<Button
customClass="time-machine-queries--new"
shape={ButtonShape.Square}
icon={IconFont.PlusSkinny}
size={ComponentSize.ExtraSmall}
color={ComponentColor.Default}
onClick={onAddQuery}
/>
</div>
<div className="time-machine-queries--buttons">
<TimeMachineQueriesTimer
status={queriesState.loading}
duration={queriesState.duration}
/>
<ComponentSpacer align={Alignment.Right}>
<SlideToggle.Label text="View Raw Data" />
<SlideToggle
active={isViewingRawData}
onChange={this.handleToggleIsViewingRawData}
size={ComponentSize.ExtraSmall}
/>
<TimeMachineRefreshDropdown />
<TimeRangeDropdown
timeRange={timeRange}
onSetTimeRange={onSetTimeRange}
/>
<TimeMachineQueriesSwitcher />
<CSVExportButton files={files} />
<SubmitQueryButton queryStatus={queriesState.loading} />
</ComponentSpacer>
</div>
</div>
<div className="time-machine-queries--body">{this.queryEditor}</div>
</div>
)
}
return (
<div className="time-machine-queries">
<div className="time-machine-queries--controls">
<div className="time-machine-queries--tabs">
{draftQueries.map((query, queryIndex) => (
<TimeMachineQueryTab
key={queryIndex}
queryIndex={queryIndex}
query={query}
/>
))}
<Button
customClass="time-machine-queries--new"
shape={ButtonShape.Square}
icon={IconFont.PlusSkinny}
size={ComponentSize.ExtraSmall}
color={ComponentColor.Default}
onClick={onAddQuery}
/>
</div>
<div className="time-machine-queries--buttons">
<TimeMachineQueriesTimer
status={queriesState.loading}
duration={queriesState.duration}
/>
<TimeMachineQueriesSwitcher />
<SubmitQueryButton queryStatus={queriesState.loading} />
</div>
</div>
<div className="time-machine-queries--body">{queryEditor}</div>
</div>
)
private get queryEditor(): JSX.Element {
const {activeQuery} = this.props
if (activeQuery.editMode === QueryEditMode.Builder) {
return <TimeMachineQueryBuilder />
} else if (activeQuery.type === InfluxLanguage.Flux) {
return <TimeMachineFluxEditor />
} else if (activeQuery.type === InfluxLanguage.InfluxQL) {
return <TimeMachineInfluxQLEditor />
}
}
private handleToggleIsViewingRawData = () => {
const {isViewingRawData, onSetIsViewingRawData} = this.props
onSetIsViewingRawData(!isViewingRawData)
}
}
const mstp = (state: AppState) => {
const {draftQueries} = getActiveTimeMachine(state)
const {draftQueries, timeRange, isViewingRawData} = getActiveTimeMachine(
state
)
const activeQuery = getActiveQuery(state)
return {activeQuery, draftQueries}
return {timeRange, activeQuery, draftQueries, isViewingRawData}
}
const mdtp = {
onAddQuery: addQuery,
onSetTimeRange: setTimeRange,
onSetIsViewingRawData: setIsViewingRawData,
}
export default connect<StateProps, DispatchProps, OwnProps>(

View File

@ -14,7 +14,7 @@
}
.task-form--options {
flex: 1 0 0;
flex: 1 0 140px;
padding: $ix-marg-d;
}

View File

@ -0,0 +1,38 @@
// Libraries
import React from 'react'
import {shallow} from 'enzyme'
// Components
import TaskForm from 'src/tasks/components/TaskForm'
import {Form} from 'src/clockface'
// Constants
import defaultTaskOptions from 'src/tasks/reducers/v2'
const setup = (override?) => {
const props = {
orgs: [],
taskOptions: defaultTaskOptions,
onChangeScheduleType: jest.fn(),
onChangeInput: jest.fn(),
onChangeTaskOrgID: jest.fn(),
...override,
}
const wrapper = shallow(<TaskForm {...props} />)
return {wrapper}
}
describe('TaskForm', () => {
describe('rendering', () => {
it('renders', () => {
const {wrapper} = setup()
const form = wrapper.find(Form)
expect(wrapper.exists()).toBe(true)
expect(form.exists()).toBe(true)
expect(wrapper).toMatchSnapshot()
})
})
})

View File

@ -11,9 +11,11 @@ import {
Input,
Radio,
ButtonShape,
Button,
ComponentColor,
ButtonType,
} from 'src/clockface'
import TaskOptionsOrgDropdown from 'src/tasks/components/TasksOptionsOrgDropdown'
import FluxEditor from 'src/shared/components/FluxEditor'
import TaskScheduleFormField from 'src/tasks/components/TaskScheduleFormField'
// Types
@ -25,132 +27,134 @@ import {Organization} from 'src/api'
import './TaskForm.scss'
interface Props {
script: string
orgs: Organization[]
taskOptions: TaskOptions
onChangeScheduleType: (schedule: TaskSchedule) => void
onChangeScript: (script: string) => void
onChangeInput: (e: ChangeEvent<HTMLInputElement>) => void
onChangeTaskOrgID: (orgID: string) => void
isInOverlay?: boolean
onSubmit?: () => void
canSubmit?: boolean
dismiss?: () => void
}
interface State {
retryAttempts: string
schedule: TaskSchedule
}
export default class TaskForm extends PureComponent<Props, State> {
public static defaultProps: Partial<Props> = {
isInOverlay: false,
onSubmit: () => {},
canSubmit: true,
dismiss: () => {},
}
constructor(props) {
super(props)
this.state = {
retryAttempts: '1',
schedule: props.taskOptions.taskScheduleType,
}
}
public render() {
const {
script,
onChangeInput,
onChangeScript,
onChangeTaskOrgID,
taskOptions: {name, taskScheduleType, interval, offset, cron, orgID},
orgs,
isInOverlay,
} = this.props
return (
<div className="task-form">
<div className="task-form--options">
<Form>
<Grid>
<Grid.Row>
<Grid.Column widthXS={Columns.Twelve}>
<Form.Element label="Name">
<Input
name="name"
placeholder="Name your task"
onChange={onChangeInput}
value={name}
/>
</Form.Element>
</Grid.Column>
<Grid.Column widthXS={Columns.Twelve}>
<Form.Element label="Owner">
<TaskOptionsOrgDropdown
orgs={orgs}
selectedOrgID={orgID}
onChangeTaskOrgID={onChangeTaskOrgID}
/>
</Form.Element>
</Grid.Column>
<Grid.Column widthXS={Columns.Twelve}>
<Form.Element label="Schedule Task">
<ComponentSpacer
align={Alignment.Left}
stackChildren={Stack.Rows}
<Form>
<Grid>
<Grid.Row>
<Grid.Column widthXS={Columns.Twelve}>
<Form.Element label="Name">
<Input
name="name"
placeholder="Name your task"
onChange={onChangeInput}
value={name}
/>
</Form.Element>
</Grid.Column>
<Grid.Column widthXS={Columns.Twelve}>
<Form.Element label="Owner">
<TaskOptionsOrgDropdown
orgs={orgs}
selectedOrgID={orgID}
onChangeTaskOrgID={onChangeTaskOrgID}
/>
</Form.Element>
</Grid.Column>
<Grid.Column>
<Form.Element label="Schedule Task">
<ComponentSpacer
align={Alignment.Left}
stackChildren={Stack.Rows}
>
<Radio shape={ButtonShape.StretchToFit}>
<Radio.Button
id="interval"
active={taskScheduleType === TaskSchedule.interval}
value={TaskSchedule.interval}
titleText="Interval"
onClick={this.handleChangeScheduleType}
>
<Radio shape={ButtonShape.StretchToFit}>
<Radio.Button
id="interval"
active={taskScheduleType === TaskSchedule.interval}
value={TaskSchedule.interval}
titleText="Interval"
onClick={this.handleChangeScheduleType}
>
Interval
</Radio.Button>
<Radio.Button
id="cron"
active={taskScheduleType === TaskSchedule.cron}
value={TaskSchedule.cron}
titleText="Cron"
onClick={this.handleChangeScheduleType}
>
Cron
</Radio.Button>
</Radio>
<TaskScheduleFormField
onChangeInput={onChangeInput}
schedule={taskScheduleType}
interval={interval}
offset={offset}
cron={cron}
/>
</ComponentSpacer>
</Form.Element>
</Grid.Column>
<Grid.Column widthXS={Columns.Twelve}>
<Form.Element label="Retry attempts">
<Input
name="retry"
placeholder=""
onChange={this.handleChangeRetry}
status={ComponentStatus.Disabled}
value={this.state.retryAttempts}
/>
</Form.Element>
</Grid.Column>
</Grid.Row>
</Grid>
</Form>
</div>
<div className="task-form--editor">
<FluxEditor
script={script}
onChangeScript={onChangeScript}
visibility="visible"
status={{text: '', type: ''}}
suggestions={[]}
/>
</div>
</div>
Interval
</Radio.Button>
<Radio.Button
id="cron"
active={taskScheduleType === TaskSchedule.cron}
value={TaskSchedule.cron}
titleText="Cron"
onClick={this.handleChangeScheduleType}
>
Cron
</Radio.Button>
</Radio>
</ComponentSpacer>
</Form.Element>
</Grid.Column>
<TaskScheduleFormField
onChangeInput={onChangeInput}
schedule={taskScheduleType}
interval={interval}
offset={offset}
cron={cron}
/>
{isInOverlay && this.buttons}
</Grid.Row>
</Grid>
</Form>
)
}
private handleChangeRetry = (e: ChangeEvent<HTMLInputElement>): void => {
const retryAttempts = e.target.value
this.setState({retryAttempts})
private get buttons(): JSX.Element {
const {onSubmit, canSubmit, dismiss} = this.props
return (
<Grid.Column widthXS={Columns.Twelve}>
<Form.Footer>
<Button
text="Cancel"
onClick={dismiss}
titleText="Cancel save"
type={ButtonType.Button}
/>
<Button
text={'Save as Task'}
color={ComponentColor.Success}
type={ButtonType.Submit}
onClick={onSubmit}
status={
canSubmit ? ComponentStatus.Default : ComponentStatus.Disabled
}
/>
</Form.Footer>
</Grid.Column>
)
}
private handleChangeScheduleType = (schedule: TaskSchedule): void => {

View File

@ -1,12 +1,13 @@
import React, {PureComponent} from 'react'
import {Page} from 'src/pageLayout'
import {ComponentColor, Button} from 'src/clockface'
import {ComponentColor, Button, ComponentStatus} from 'src/clockface'
import 'src/tasks/components/TasksPage.scss'
interface Props {
title: string
canSubmit: boolean
onCancel: () => void
onSave: () => void
}
@ -27,6 +28,11 @@ export default class TaskHeader extends PureComponent<Props> {
<Button
color={ComponentColor.Success}
text="Save"
status={
this.props.canSubmit
? ComponentStatus.Default
: ComponentStatus.Disabled
}
onClick={this.props.onSave}
/>
</Page.Header.Right>

View File

@ -2,10 +2,9 @@
import React, {PureComponent, ChangeEvent} from 'react'
// Components
import {ComponentSpacer, Input, InputType} from 'src/clockface'
import {Input, InputType, Grid, Form, Columns} from 'src/clockface'
// Types
import {Alignment} from 'src/clockface'
import {TaskSchedule} from 'src/utils/taskOptionsToFluxScript'
interface Props {
@ -22,31 +21,33 @@ export default class TaskScheduleFormFields extends PureComponent<Props> {
return (
<>
<ComponentSpacer align={Alignment.Left} stretchToFitWidth={true}>
<label className="task-form--form-label">
{schedule === TaskSchedule.interval ? 'Interval' : 'Cron'}
</label>
<Input
name={schedule}
type={InputType.Text}
placeholder={
schedule === TaskSchedule.interval ? '1d3h30s' : '0 2 * * *'
}
value={schedule === TaskSchedule.interval ? interval : cron}
onChange={this.props.onChangeInput}
/>
</ComponentSpacer>
<Grid.Column widthXS={Columns.Six}>
<Form.Element
label={schedule === TaskSchedule.interval ? 'Interval' : 'Cron'}
>
<Input
name={schedule}
type={InputType.Text}
placeholder={
schedule === TaskSchedule.interval ? '1d3h30s' : '0 2 * * *'
}
value={schedule === TaskSchedule.interval ? interval : cron}
onChange={this.props.onChangeInput}
/>
</Form.Element>
</Grid.Column>
<ComponentSpacer align={Alignment.Left} stretchToFitWidth={true}>
<label className="task-form--form-label">Offset</label>
<Input
name="offset"
type={InputType.Text}
value={offset}
placeholder="20m"
onChange={onChangeInput}
/>
</ComponentSpacer>
<Grid.Column widthXS={Columns.Six}>
<Form.Element label="Offset">
<Input
name="offset"
type={InputType.Text}
value={offset}
placeholder="20m"
onChange={onChangeInput}
/>
</Form.Element>
</Grid.Column>
</>
)
}

View File

@ -0,0 +1,87 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TaskForm rendering renders 1`] = `
<Form>
<Grid>
<GridRow>
<GridColumn
widthXS={12}
>
<FormElement
label="Name"
>
<Input
autoFocus={false}
autocomplete="off"
disabledTitleText="This input is disabled"
name="name"
onChange={[MockFunction]}
placeholder="Name your task"
size="sm"
spellCheck={false}
status="default"
titleText=""
value=""
/>
</FormElement>
</GridColumn>
<GridColumn
widthXS={12}
>
<FormElement
label="Owner"
>
<TaskOptionsOrgDropdown
onChangeTaskOrgID={[MockFunction]}
orgs={Array []}
/>
</FormElement>
</GridColumn>
<GridColumn
widthXS={12}
>
<FormElement
label="Schedule Task"
>
<ComponentSpacer
align="left"
stackChildren="rows"
>
<Radio
color="primary"
shape="stretch"
size="sm"
>
<RadioButton
active={false}
disabled={false}
disabledTitleText="This option is disabled"
id="interval"
onClick={[Function]}
titleText="Interval"
value="interval"
>
Interval
</RadioButton>
<RadioButton
active={false}
disabled={false}
disabledTitleText="This option is disabled"
id="cron"
onClick={[Function]}
titleText="Cron"
value="cron"
>
Cron
</RadioButton>
</Radio>
</ComponentSpacer>
</FormElement>
</GridColumn>
<TaskScheduleFormFields
onChangeInput={[MockFunction]}
/>
</GridRow>
</Grid>
</Form>
`;

View File

@ -8,7 +8,7 @@ import {connect} from 'react-redux'
import TaskForm from 'src/tasks/components/TaskForm'
import TaskHeader from 'src/tasks/components/TaskHeader'
import {Page} from 'src/pageLayout'
import {Task as TaskAPI, User, Organization} from 'src/api'
import FluxEditor from 'src/shared/components/FluxEditor'
// Actions
import {
@ -22,6 +22,7 @@ import {
} from 'src/tasks/actions/v2'
// Types
import {Task as TaskAPI, User, Organization} from 'src/api'
import {Links} from 'src/types/v2/links'
import {State as TasksState} from 'src/tasks/reducers/v2'
import {
@ -88,24 +89,47 @@ class TaskPage extends PureComponent<
<Page titleTag={`Edit ${taskOptions.name}`}>
<TaskHeader
title="Update Task"
canSubmit={this.isFormValid}
onCancel={this.handleCancel}
onSave={this.handleSave}
/>
<Page.Contents fullWidth={true} scrollable={false}>
<TaskForm
orgs={orgs}
script={currentScript}
taskOptions={taskOptions}
onChangeScript={this.handleChangeScript}
onChangeScheduleType={this.handleChangeScheduleType}
onChangeInput={this.handleChangeInput}
onChangeTaskOrgID={this.handleChangeTaskOrgID}
/>
<div className="task-form">
<div className="task-form--options">
<TaskForm
orgs={orgs}
canSubmit={this.isFormValid}
taskOptions={taskOptions}
onChangeInput={this.handleChangeInput}
onChangeScheduleType={this.handleChangeScheduleType}
onChangeTaskOrgID={this.handleChangeTaskOrgID}
/>
</div>
<div className="task-form--editor">
<FluxEditor
script={currentScript}
onChangeScript={this.handleChangeScript}
visibility="visible"
status={{text: '', type: ''}}
suggestions={[]}
/>
</div>
</div>
</Page.Contents>
</Page>
)
}
private get isFormValid(): boolean {
const {
taskOptions: {name, cron, interval},
currentScript,
} = this.props
const hasSchedule = !!cron || !!interval
return hasSchedule && !!name && !!currentScript
}
private handleChangeScript = (script: string) => {
this.props.setCurrentScript(script)
}

View File

@ -3,11 +3,13 @@ import React, {PureComponent, ChangeEvent} from 'react'
import {InjectedRouter} from 'react-router'
import {connect} from 'react-redux'
// components
import TaskForm from 'src/tasks/components/TaskForm'
import TaskHeader from 'src/tasks/components/TaskHeader'
import FluxEditor from 'src/shared/components/FluxEditor'
import {Page} from 'src/pageLayout'
import {Links} from 'src/types/v2/links'
// actions
import {State as TasksState} from 'src/tasks/reducers/v2'
import {
setNewScript,
@ -16,12 +18,17 @@ import {
setTaskOption,
clearTask,
} from 'src/tasks/actions/v2'
// types
import {Links} from 'src/types/v2/links'
import {Organization} from 'src/types/v2'
import {
TaskOptions,
TaskOptionKeys,
TaskSchedule,
} from 'src/utils/taskOptionsToFluxScript'
import {Organization} from 'src/types/v2'
// Styles
import 'src/tasks/components/TaskForm.scss'
interface PassedInProps {
router: InjectedRouter
@ -67,24 +74,47 @@ class TaskPage extends PureComponent<
<Page titleTag="Create Task">
<TaskHeader
title="Create Task"
canSubmit={this.isFormValid}
onCancel={this.handleCancel}
onSave={this.handleSave}
/>
<Page.Contents fullWidth={true} scrollable={false}>
<TaskForm
orgs={orgs}
script={newScript}
taskOptions={taskOptions}
onChangeInput={this.handleChangeInput}
onChangeScheduleType={this.handleChangeScheduleType}
onChangeScript={this.handleChangeScript}
onChangeTaskOrgID={this.handleChangeTaskOrgID}
/>
<div className="task-form">
<div className="task-form--options">
<TaskForm
orgs={orgs}
canSubmit={this.isFormValid}
taskOptions={taskOptions}
onChangeInput={this.handleChangeInput}
onChangeScheduleType={this.handleChangeScheduleType}
onChangeTaskOrgID={this.handleChangeTaskOrgID}
/>
</div>
<div className="task-form--editor">
<FluxEditor
script={newScript}
onChangeScript={this.handleChangeScript}
visibility="visible"
status={{text: '', type: ''}}
suggestions={[]}
/>
</div>
</div>
</Page.Contents>
</Page>
)
}
private get isFormValid(): boolean {
const {
taskOptions: {name, cron, interval},
newScript,
} = this.props
const hasSchedule = !!cron || !!interval
return hasSchedule && !!name && !!newScript
}
private handleChangeScript = (script: string) => {
this.props.setNewScript(script)
}

View File

@ -18,7 +18,7 @@ export interface State {
taskOptions: TaskOptions
}
const defaultTaskOptions: TaskOptions = {
export const defaultTaskOptions: TaskOptions = {
name: '',
interval: '',
offset: '',