<commit_before>package chronograf

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"strconv"
	"strings"
	"time"
	"unicode"
	"unicode/utf8"

	"github.com/influxdata/influxdb/influxql"
)

// General errors.
const (
	ErrUpstreamTimeout   = Error("request to backend timed out")
	ErrSourceNotFound    = Error("source not found")
	ErrServerNotFound    = Error("server not found")
	ErrLayoutNotFound    = Error("layout not found")
	ErrDashboardNotFound = Error("dashboard not found")
	ErrUserNotFound      = Error("user not found")
	ErrLayoutInvalid     = Error("layout is invalid")
	ErrAlertNotFound     = Error("alert not found")
	ErrAuthentication    = Error("user not authenticated")
	ErrUninitialized     = Error("client uninitialized. Call Open() method")
)

// Error is a domain error encountered while processing chronograf requests
type Error string

func (e Error) Error() string {
	return string(e)
}

// Logger represents an abstracted structured logging implementation. It
// provides methods to trigger log messages at various alert levels and a
// WithField method to set keys for a structured log message.
type Logger interface {
	Debug(...interface{})
	Info(...interface{})
	Error(...interface{})

	WithField(string, interface{}) Logger

	// Logger can be transformed into an io.Writer.
	// That writer is the end of an io.Pipe and it is your responsibility to close it.
	Writer() *io.PipeWriter
}

// Router is an abstracted Router based on the API provided by the
// julienschmidt/httprouter package.
type Router interface {
	http.Handler
	GET(string, http.HandlerFunc)
	PATCH(string, http.HandlerFunc)
	POST(string, http.HandlerFunc)
	DELETE(string, http.HandlerFunc)
	PUT(string, http.HandlerFunc)

	Handler(string, string, http.Handler)
}

// Assets returns a handler to serve the website.
type Assets interface {
	Handler() http.Handler
}

// Supported time-series databases
const (
	// InfluxDB is the open-source time-series database
	InfluxDB = "influx"
	// InfluxEnteprise is the clustered HA time-series database
	InfluxEnterprise = "influx-enterprise"
	// InfluxRelay is the basic HA layer over InfluxDB
	InfluxRelay = "influx-relay"
)

// TSDBStatus represents the current status of a time series database
type TSDBStatus interface {
	// Connect will connect to the time series using the information in `Source`.
	Connect(ctx context.Context, src *Source) error
	// Ping returns version and TSDB type of time series database if reachable.
	Ping(context.Context) error
	// Version returns the version of the TSDB database
	Version(context.Context) (string, error)
	// Type returns the type of the TSDB database
	Type(context.Context) (string, error)
}

// TimeSeries represents a queryable time series database.
type TimeSeries interface {
	// Query retrieves time series data from the database.
	Query(context.Context, Query) (Response, error)
	// Connect will connect to the time series using the information in `Source`.
	Connect(context.Context, *Source) error
	// UsersStore represents the user accounts within the TimeSeries database
	Users(context.Context) UsersStore
	// Permissions returns all valid names permissions in this database
	Permissions(context.Context) Permissions
	// Roles represents the roles associated with this TimesSeriesDatabase
	Roles(context.Context) (RolesStore, error)
}

// Role is a restricted set of permissions assigned to a set of users.
type Role struct {
	Name        string      `json:"name"`
	Permissions Permissions `json:"permissions,omitempty"`
	Users       []User      `json:"users,omitempty"`
}

// RolesStore is the Storage and retrieval of authentication information
type RolesStore interface {
	// All lists all roles from the RolesStore
	All(context.Context) ([]Role, error)
	// Create a new Role in the RolesStore
	Add(context.Context, *Role) (*Role, error)
	// Delete the Role from the RolesStore
	Delete(context.Context, *Role) error
	// Get retrieves a role if name exists.
	Get(ctx context.Context, name string) (*Role, error)
	// Update the roles' users or permissions
	Update(context.Context, *Role) error
}

// Range represents an upper and lower bound for data
type Range struct {
	Upper int64 `json:"upper"` // Upper is the upper bound
	Lower int64 `json:"lower"` // Lower is the lower bound
}

type TemplateVariable interface {
	fmt.Stringer
	Name() string     // returns the variable name
	Precedence() uint // ordinal indicating precedence level for replacement
}

type ExecutableVar interface {
	Exec(string)
}

// TemplateValue is a value use to replace a template in an InfluxQL query
type BasicTemplateValue struct {
	Value    string `json:"value"`    // Value is the specific value used to replace a template in an InfluxQL query
	Type     string `json:"type"`     // Type can be tagKey, tagValue, fieldKey, csv, measurement, database, constant
	Selected bool   `json:"selected"` // Selected states that this variable has been picked to use for replacement
}

// TemplateVar is a named variable within an InfluxQL query to be replaced with Values
type BasicTemplateVar struct {
	Var    string               `json:"tempVar"` // Var is the string to replace within InfluxQL
	Values []BasicTemplateValue `json:"values"`  // Values are the replacement values within InfluxQL
}

func (t BasicTemplateVar) Name() string {
	return t.Var
}

// String converts the template variable into a correct InfluxQL string based
// on its type
func (t BasicTemplateVar) String() string {
	if len(t.Values) == 0 {
		return ""
	}
	switch t.Values[0].Type {
	case "tagKey", "fieldKey", "measurement", "database":
		return `"` + t.Values[0].Value + `"`
	case "tagValue":
		return `'` + t.Values[0].Value + `'`
	case "csv", "constant":
		return t.Values[0].Value
	default:
		return ""
	}
}

func (t BasicTemplateVar) Precedence() uint {
	return 0
}

type GroupByVar struct {
	Var               string        `json:"tempVar"`                     // the name of the variable as present in the query
	Duration          time.Duration `json:"duration,omitempty"`          // the Duration supplied by the query
	Resolution        uint          `json:"resolution"`                  // the available screen resolution to render the results of this query
	ReportingInterval time.Duration `json:"reportingInterval,omitempty"` // the interval at which data is reported to this series
}

// Exec is responsible for extracting the Duration from the query
func (g *GroupByVar) Exec(query string) {
	whereClause := "WHERE time > now() - "
	start := strings.Index(query, whereClause)
	if start == -1 {
		// no where clause
		return
	}

	// reposition start to the END of the where clause
	durStr := query[start+len(whereClause):]

	// advance to next space
	pos := 0
	for pos < len(durStr) {
		rn, _ := utf8.DecodeRuneInString(durStr[pos:])
		if unicode.IsSpace(rn) {
			break
		}
		pos++
	}

	dur, err := influxql.ParseDuration(durStr[:pos])
	if err != nil {
		return
	}

	g.Duration = dur
}

func (g *GroupByVar) String() string {
	intervalNS := g.ReportingInterval.Nanoseconds()
	// prevent division by zero
	if intervalNS == 0 || g.Resolution == 0 {
		return " "
	}

	//TODO(timraymond): ascertain group by resolution
	duration := g.Duration.Nanoseconds() / g.ReportingInterval.Nanoseconds() * int64(g.Resolution)
	return "time(" + strconv.Itoa(int(duration)/1000000) + "s)"
}

func (g *GroupByVar) Name() string {
	return g.Var
}

func (g *GroupByVar) Precedence() uint {
	return 1
}

// TemplateID is the unique ID used to identify a template
type TemplateID string

// Template represents a series of choices to replace TemplateVars within InfluxQL
type Template struct {
	BasicTemplateVar
	ID    TemplateID     `json:"id"`              // ID is the unique ID associated with this template
	Type  string         `json:"type"`            // Type can be fieldKeys, tagKeys, tagValues, CSV, constant, query, measurements, databases
	Label string         `json:"label"`           // Label is a user-facing description of the Template
	Query *TemplateQuery `json:"query,omitempty"` // Query is used to generate the choices for a template
}

// Query retrieves a Response from a TimeSeries.
type Query struct {
	Command      string       `json:"query"`              // Command is the query itself
	DB           string       `json:"db,omitempty"`       // DB is optional and if empty will not be used.
	RP           string       `json:"rp,omitempty"`       // RP is a retention policy and optional; if empty will not be used.
	TemplateVars TemplateVars `json:"tempVars,omitempty"` // TemplateVars are template variables to replace within an InfluxQL query
	Wheres       []string     `json:"wheres,omitempty"`   // Wheres restricts the query to certain attributes
	GroupBys     []string     `json:"groupbys,omitempty"` // GroupBys collate the query by these tags
	Resolution   uint         `json:"resolution,omitempty"`
	Label        string       `json:"label,omitempty"` // Label is the Y-Axis label for the data
	Range        *Range       `json:"range,omitempty"` // Range is the default Y-Axis range for the data
}

// TemplateVars are a hetergenous collection of different TemplateVariables
// with the capability to decode arbitrary JSON into the appropriate template
// variable type
type TemplateVars []TemplateVariable

func (t *TemplateVars) UnmarshalJSON(text []byte) error {
	rawVars := bytes.NewReader(text)
	dec := json.NewDecoder(rawVars)

	// read open bracket
	rawTok, err := dec.Token()
	if err != nil {
		return err
	}

	tok, isDelim := rawTok.(json.Delim)
	if !isDelim || tok != '[' {
		return errors.New("Expected JSON array, but found " + tok.String())
	}
<commit_msg>Documentation++<commit_after>package chronograf

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"strconv"
	"strings"
	"time"
	"unicode"
	"unicode/utf8"

	"github.com/influxdata/influxdb/influxql"
)

// General errors.
const (
	ErrUpstreamTimeout   = Error("request to backend timed out")
	ErrSourceNotFound    = Error("source not found")
	ErrServerNotFound    = Error("server not found")
	ErrLayoutNotFound    = Error("layout not found")
	ErrDashboardNotFound = Error("dashboard not found")
	ErrUserNotFound      = Error("user not found")
	ErrLayoutInvalid     = Error("layout is invalid")
	ErrAlertNotFound     = Error("alert not found")
	ErrAuthentication    = Error("user not authenticated")
	ErrUninitialized     = Error("client uninitialized. Call Open() method")
)

// Error is a domain error encountered while processing chronograf requests
type Error string

func (e Error) Error() string {
	return string(e)
}

// Logger represents an abstracted structured logging implementation. It
// provides methods to trigger log messages at various alert levels and a
// WithField method to set keys for a structured log message.
type Logger interface {
	Debug(...interface{})
	Info(...interface{})
	Error(...interface{})

	WithField(string, interface{}) Logger

	// Logger can be transformed into an io.Writer.
	// That writer is the end of an io.Pipe and it is your responsibility to close it.
	Writer() *io.PipeWriter
}

// Router is an abstracted Router based on the API provided by the
// julienschmidt/httprouter package.
type Router interface {
	http.Handler
	GET(string, http.HandlerFunc)
	PATCH(string, http.HandlerFunc)
	POST(string, http.HandlerFunc)
	DELETE(string, http.HandlerFunc)
	PUT(string, http.HandlerFunc)

	Handler(string, string, http.Handler)
}

// Assets returns a handler to serve the website.
type Assets interface {
	Handler() http.Handler
}

// Supported time-series databases
const (
	// InfluxDB is the open-source time-series database
	InfluxDB = "influx"
	// InfluxEnteprise is the clustered HA time-series database
	InfluxEnterprise = "influx-enterprise"
	// InfluxRelay is the basic HA layer over InfluxDB
	InfluxRelay = "influx-relay"
)

// TSDBStatus represents the current status of a time series database
type TSDBStatus interface {
	// Connect will connect to the time series using the information in `Source`.
	Connect(ctx context.Context, src *Source) error
	// Ping returns version and TSDB type of time series database if reachable.
	Ping(context.Context) error
	// Version returns the version of the TSDB database
	Version(context.Context) (string, error)
	// Type returns the type of the TSDB database
	Type(context.Context) (string, error)
}

// TimeSeries represents a queryable time series database.
type TimeSeries interface {
	// Query retrieves time series data from the database.
	Query(context.Context, Query) (Response, error)
	// Connect will connect to the time series using the information in `Source`.
	Connect(context.Context, *Source) error
	// UsersStore represents the user accounts within the TimeSeries database
	Users(context.Context) UsersStore
	// Permissions returns all valid names permissions in this database
	Permissions(context.Context) Permissions
	// Roles represents the roles associated with this TimesSeriesDatabase
	Roles(context.Context) (RolesStore, error)
}

// Role is a restricted set of permissions assigned to a set of users.
type Role struct {
	Name        string      `json:"name"`
	Permissions Permissions `json:"permissions,omitempty"`
	Users       []User      `json:"users,omitempty"`
}

// RolesStore is the Storage and retrieval of authentication information
type RolesStore interface {
	// All lists all roles from the RolesStore
	All(context.Context) ([]Role, error)
	// Create a new Role in the RolesStore
	Add(context.Context, *Role) (*Role, error)
	// Delete the Role from the RolesStore
	Delete(context.Context, *Role) error
	// Get retrieves a role if name exists.
	Get(ctx context.Context, name string) (*Role, error)
	// Update the roles' users or permissions
	Update(context.Context, *Role) error
}

// Range represents an upper and lower bound for data
type Range struct {
	Upper int64 `json:"upper"` // Upper is the upper bound
	Lower int64 `json:"lower"` // Lower is the lower bound
}

type TemplateVariable interface {
	fmt.Stringer
	Name() string     // returns the variable name
	Precedence() uint // ordinal indicating precedence level for replacement
}

type ExecutableVar interface {
	Exec(string)
}

// TemplateValue is a value use to replace a template in an InfluxQL query
type BasicTemplateValue struct {
	Value    string `json:"value"`    // Value is the specific value used to replace a template in an InfluxQL query
	Type     string `json:"type"`     // Type can be tagKey, tagValue, fieldKey, csv, measurement, database, constant
	Selected bool   `json:"selected"` // Selected states that this variable has been picked to use for replacement
}

// TemplateVar is a named variable within an InfluxQL query to be replaced with Values
type BasicTemplateVar struct {
	Var    string               `json:"tempVar"` // Var is the string to replace within InfluxQL
	Values []BasicTemplateValue `json:"values"`  // Values are the replacement values within InfluxQL
}

func (t BasicTemplateVar) Name() string {
	return t.Var
}

// String converts the template variable into a correct InfluxQL string based
// on its type
func (t BasicTemplateVar) String() string {
	if len(t.Values) == 0 {
		return ""
	}
	switch t.Values[0].Type {
	case "tagKey", "fieldKey", "measurement", "database":
		return `"` + t.Values[0].Value + `"`
	case "tagValue":
		return `'` + t.Values[0].Value + `'`
	case "csv", "constant":
		return t.Values[0].Value
	default:
		return ""
	}
}

func (t BasicTemplateVar) Precedence() uint {
	return 0
}

type GroupByVar struct {
	Var               string        `json:"tempVar"`                     // the name of the variable as present in the query
	Duration          time.Duration `json:"duration,omitempty"`          // the Duration supplied by the query
	Resolution        uint          `json:"resolution"`                  // the available screen resolution to render the results of this query
	ReportingInterval time.Duration `json:"reportingInterval,omitempty"` // the interval at which data is reported to this series
}

// Exec is responsible for extracting the Duration from the query
func (g *GroupByVar) Exec(query string) {
	whereClause := "WHERE time > now() - "
	start := strings.Index(query, whereClause)
	if start == -1 {
		// no where clause
		return
	}

	// reposition start to the END of the where clause
	durStr := query[start+len(whereClause):]

	// advance to next space
	pos := 0
	for pos < len(durStr) {
		rn, _ := utf8.DecodeRuneInString(durStr[pos:])
		if unicode.IsSpace(rn) {
			break
		}
		pos++
	}

	dur, err := influxql.ParseDuration(durStr[:pos])
	if err != nil {
		return
	}

	g.Duration = dur
}

func (g *GroupByVar) String() string {
	intervalNS := g.ReportingInterval.Nanoseconds()
	// prevent division by zero
	if intervalNS == 0 || g.Resolution == 0 {
		return " "
	}

	//TODO(timraymond): ascertain group by resolution
	duration := g.Duration.Nanoseconds() / g.ReportingInterval.Nanoseconds() * int64(g.Resolution)
	return "time(" + strconv.Itoa(int(duration)/1000000) + "s)"
}

func (g *GroupByVar) Name() string {
	return g.Var
}

func (g *GroupByVar) Precedence() uint {
	return 1
}

// TemplateID is the unique ID used to identify a template
type TemplateID string

// Template represents a series of choices to replace TemplateVars within InfluxQL
type Template struct {
	BasicTemplateVar
	ID    TemplateID     `json:"id"`              // ID is the unique ID associated with this template
	Type  string         `json:"type"`            // Type can be fieldKeys, tagKeys, tagValues, CSV, constant, query, measurements, databases
	Label string         `json:"label"`           // Label is a user-facing description of the Template
	Query *TemplateQuery `json:"query,omitempty"` // Query is used to generate the choices for a template
}

// Query retrieves a Response from a TimeSeries.
type Query struct {
	Command      string       `json:"query"`                // Command is the query itself
	DB           string       `json:"db,omitempty"`         // DB is optional and if empty will not be used.
	RP           string       `json:"rp,omitempty"`         // RP is a retention policy and optional; if empty will not be used.
	TemplateVars TemplateVars `json:"tempVars,omitempty"`   // TemplateVars are template variables to replace within an InfluxQL query
	Wheres       []string     `json:"wheres,omitempty"`     // Wheres restricts the query to certain attributes
	GroupBys     []string     `json:"groupbys,omitempty"`   // GroupBys collate the query by these tags
	Resolution   uint         `json:"resolution,omitempty"` // Resolution is the available screen resolution to render query results
	Label        string       `json:"label,omitempty"`      // Label is the Y-Axis label for the data
	Range        *Range       `json:"range,omitempty"`      // Range is the default Y-Axis range for the data
}

// TemplateVars are a heterogeneous collection of different TemplateVariables
// with the capability to decode arbitrary JSON into the appropriate template
// variable type
type TemplateVars []TemplateVariable

func (t *TemplateVars) UnmarshalJSON(text []byte) error {
	rawVars := bytes.NewReader(text)
	dec := json.NewDecoder(rawVars)

	// read open bracket
	rawTok, err := dec.Token()
	if err != nil {
		return err
	}

	tok, isDelim := rawTok.(json.Delim)
	if !isDelim || tok != '[' {
		return errors.New("Expected JSON array, but found " + tok.String())
	}

{'fraction_non_alphanumeric': 0.09594323615610056, 'fraction_numerical': 0.0026736593141035527, 'mean_word_length': 3.5829406220546653, 'pattern_counts': {'":': 6, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1144060', 'n_tokens_mistral': 5629, 'n_tokens_neox': 5459, 'n_words': 2120}