mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
transform: add expressions to query editor (w/ feature flag) (#20072)
for use with gel which is not released yet.
This commit is contained in:
parent
2bb4684741
commit
861eb72113
@ -203,6 +203,7 @@
|
||||
"angular-native-dragdrop": "1.2.2",
|
||||
"angular-route": "1.6.6",
|
||||
"angular-sanitize": "1.6.6",
|
||||
"apache-arrow": "0.15.0",
|
||||
"baron": "3.0.3",
|
||||
"brace": "0.10.0",
|
||||
"calculate-size": "1.1.1",
|
||||
|
@ -12,6 +12,7 @@ export interface BuildInfo {
|
||||
|
||||
interface FeatureToggles {
|
||||
transformations: boolean;
|
||||
expressions: boolean;
|
||||
}
|
||||
export class GrafanaBootConfig {
|
||||
datasources: { [str: string]: DataSourceInstanceSettings } = {};
|
||||
@ -46,6 +47,7 @@ export class GrafanaBootConfig {
|
||||
pluginsToPreload: string[] = [];
|
||||
featureToggles: FeatureToggles = {
|
||||
transformations: false,
|
||||
expressions: false,
|
||||
};
|
||||
|
||||
constructor(options: GrafanaBootConfig) {
|
||||
|
@ -328,7 +328,6 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
// metrics
|
||||
apiRoute.Post("/tsdb/query", bind(dtos.MetricRequest{}), Wrap(hs.QueryMetrics))
|
||||
apiRoute.Post("/tsdb/query/v2", bind(dtos.MetricRequest{}), Wrap(hs.QueryMetricsV2))
|
||||
apiRoute.Post("/tsdb/transform", bind(dtos.MetricRequest{}), Wrap(hs.Transform))
|
||||
apiRoute.Get("/tsdb/testdata/scenarios", Wrap(GetTestDataScenarios))
|
||||
apiRoute.Get("/tsdb/testdata/gensql", reqGrafanaAdmin, Wrap(GenerateSQLTestData))
|
||||
apiRoute.Get("/tsdb/testdata/random-walk", Wrap(GetTestDataRandomWalk))
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"sort"
|
||||
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
@ -21,37 +22,40 @@ func (hs *HTTPServer) QueryMetricsV2(c *m.ReqContext, reqDto dtos.MetricRequest)
|
||||
return Error(404, "Expressions feature toggle is not enabled", nil)
|
||||
}
|
||||
|
||||
timeRange := tsdb.NewTimeRange(reqDto.From, reqDto.To)
|
||||
|
||||
if len(reqDto.Queries) == 0 {
|
||||
return Error(400, "No queries found in query", nil)
|
||||
return Error(500, "No queries found in query", nil)
|
||||
}
|
||||
|
||||
var datasourceID int64
|
||||
for _, query := range reqDto.Queries {
|
||||
request := &tsdb.TsdbQuery{
|
||||
TimeRange: tsdb.NewTimeRange(reqDto.From, reqDto.To),
|
||||
Debug: reqDto.Debug,
|
||||
}
|
||||
|
||||
expr := false
|
||||
var ds *m.DataSource
|
||||
for i, query := range reqDto.Queries {
|
||||
name, err := query.Get("datasource").String()
|
||||
if err != nil {
|
||||
return Error(500, "datasource missing name", err)
|
||||
}
|
||||
datasourceID, err = query.Get("datasourceId").Int64()
|
||||
if name == "__expr__" {
|
||||
expr = true
|
||||
}
|
||||
|
||||
datasourceID, err := query.Get("datasourceId").Int64()
|
||||
if err != nil {
|
||||
return Error(400, "GEL datasource missing ID", nil)
|
||||
return Error(500, "datasource missing ID", nil)
|
||||
}
|
||||
if name == "-- GEL --" {
|
||||
break
|
||||
}
|
||||
}
|
||||
ds, err := hs.DatasourceCache.GetDatasource(datasourceID, c.SignedInUser, c.SkipCache)
|
||||
if err != nil {
|
||||
if err == m.ErrDataSourceAccessDenied {
|
||||
return Error(403, "Access denied to datasource", err)
|
||||
}
|
||||
return Error(500, "Unable to load datasource meta data", err)
|
||||
}
|
||||
|
||||
request := &tsdb.TsdbQuery{TimeRange: timeRange, Debug: reqDto.Debug}
|
||||
|
||||
for _, query := range reqDto.Queries {
|
||||
if i == 0 && !expr {
|
||||
ds, err = hs.DatasourceCache.GetDatasource(datasourceID, c.SignedInUser, c.SkipCache)
|
||||
if err != nil {
|
||||
if err == m.ErrDataSourceAccessDenied {
|
||||
return Error(403, "Access denied to datasource", err)
|
||||
}
|
||||
return Error(500, "Unable to load datasource meta data", err)
|
||||
}
|
||||
}
|
||||
request.Queries = append(request.Queries, &tsdb.Query{
|
||||
RefId: query.Get("refId").MustString("A"),
|
||||
MaxDataPoints: query.Get("maxDataPoints").MustInt64(100),
|
||||
@ -59,11 +63,21 @@ func (hs *HTTPServer) QueryMetricsV2(c *m.ReqContext, reqDto dtos.MetricRequest)
|
||||
Model: query,
|
||||
DataSource: ds,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
resp, err := tsdb.HandleRequest(c.Req.Context(), ds, request)
|
||||
if err != nil {
|
||||
return Error(500, "Metric request error", err)
|
||||
var resp *tsdb.Response
|
||||
var err error
|
||||
if !expr {
|
||||
resp, err = tsdb.HandleRequest(c.Req.Context(), ds, request)
|
||||
if err != nil {
|
||||
return Error(500, "Metric request error", err)
|
||||
}
|
||||
} else {
|
||||
resp, err = plugins.Transform.Transform(c.Req.Context(), request)
|
||||
if err != nil {
|
||||
return Error(500, "Transform request error", err)
|
||||
}
|
||||
}
|
||||
|
||||
statusCode := 200
|
||||
|
@ -1,79 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tsdb"
|
||||
)
|
||||
|
||||
// POST /api/tsdb/transform
|
||||
// This enpoint is tempory, will be part of v2 query endpoint.
|
||||
func (hs *HTTPServer) Transform(c *m.ReqContext, reqDto dtos.MetricRequest) Response {
|
||||
if !setting.IsExpressionsEnabled() {
|
||||
return Error(404, "Expressions feature toggle is not enabled", nil)
|
||||
}
|
||||
if plugins.Transform == nil {
|
||||
return Error(http.StatusServiceUnavailable, "transform plugin is not loaded", nil)
|
||||
}
|
||||
|
||||
timeRange := tsdb.NewTimeRange(reqDto.From, reqDto.To)
|
||||
|
||||
if len(reqDto.Queries) == 0 {
|
||||
return Error(400, "No queries found in query", nil)
|
||||
}
|
||||
|
||||
var datasourceID int64
|
||||
for _, query := range reqDto.Queries {
|
||||
name, err := query.Get("datasource").String()
|
||||
if err != nil {
|
||||
return Error(500, "datasource missing name", err)
|
||||
}
|
||||
datasourceID, err = query.Get("datasourceId").Int64()
|
||||
if err != nil {
|
||||
return Error(400, "GEL datasource missing ID", nil)
|
||||
}
|
||||
if name == "-- GEL --" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
ds, err := hs.DatasourceCache.GetDatasource(datasourceID, c.SignedInUser, c.SkipCache)
|
||||
if err != nil {
|
||||
if err == m.ErrDataSourceAccessDenied {
|
||||
return Error(403, "Access denied to datasource", err)
|
||||
}
|
||||
return Error(500, "Unable to load datasource meta data", err)
|
||||
}
|
||||
|
||||
request := &tsdb.TsdbQuery{TimeRange: timeRange, Debug: reqDto.Debug}
|
||||
|
||||
for _, query := range reqDto.Queries {
|
||||
request.Queries = append(request.Queries, &tsdb.Query{
|
||||
RefId: query.Get("refId").MustString("A"),
|
||||
MaxDataPoints: query.Get("maxDataPoints").MustInt64(100),
|
||||
IntervalMs: query.Get("intervalMs").MustInt64(1000),
|
||||
Model: query,
|
||||
DataSource: ds,
|
||||
})
|
||||
}
|
||||
|
||||
resp, err := plugins.Transform.Transform(c.Req.Context(), ds, request)
|
||||
if err != nil {
|
||||
return Error(500, "Transform request error", err)
|
||||
}
|
||||
|
||||
statusCode := 200
|
||||
for _, res := range resp.Results {
|
||||
if res.Error != nil {
|
||||
res.ErrorString = res.Error.Error()
|
||||
resp.Message = res.ErrorString
|
||||
statusCode = 400
|
||||
}
|
||||
}
|
||||
|
||||
return JSON(statusCode, &resp)
|
||||
}
|
@ -27,7 +27,6 @@ type TransformPlugin struct {
|
||||
|
||||
Executable string `json:"executable,omitempty"`
|
||||
|
||||
//transform.TransformPlugin
|
||||
*TransformWrapper
|
||||
|
||||
client *plugin.Client
|
||||
@ -136,22 +135,10 @@ type TransformWrapper struct {
|
||||
api *grafanaAPI
|
||||
}
|
||||
|
||||
func (tw *TransformWrapper) Transform(ctx context.Context, ds *models.DataSource, query *tsdb.TsdbQuery) (*tsdb.Response, error) {
|
||||
jsonData, err := ds.JsonData.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (tw *TransformWrapper) Transform(ctx context.Context, query *tsdb.TsdbQuery) (*tsdb.Response, error) {
|
||||
pbQuery := &pluginv2.TransformRequest{
|
||||
Datasource: &pluginv2.DatasourceInfo{
|
||||
Name: ds.Name,
|
||||
Type: ds.Type,
|
||||
Url: ds.Url,
|
||||
Id: ds.Id,
|
||||
OrgId: ds.OrgId,
|
||||
JsonData: string(jsonData),
|
||||
DecryptedSecureJsonData: ds.SecureJsonData.Decrypt(),
|
||||
},
|
||||
// TODO Not sure Datasource property needs be on this?
|
||||
Datasource: &pluginv2.DatasourceInfo{},
|
||||
TimeRange: &pluginv2.TimeRange{
|
||||
FromRaw: query.TimeRange.From,
|
||||
ToRaw: query.TimeRange.To,
|
||||
@ -217,6 +204,7 @@ func (s *grafanaAPI) QueryDatasource(ctx context.Context, req *pluginv2.QueryDat
|
||||
if len(req.Queries) == 0 {
|
||||
return nil, fmt.Errorf("zero queries found in datasource request")
|
||||
}
|
||||
|
||||
getDsInfo := &models.GetDataSourceByIdQuery{
|
||||
Id: req.DatasourceId,
|
||||
OrgId: req.OrgId,
|
||||
|
@ -29,6 +29,7 @@ import { PluginHelp } from 'app/core/components/PluginHelp/PluginHelp';
|
||||
import { addQuery } from 'app/core/utils/query';
|
||||
import { Unsubscribable } from 'rxjs';
|
||||
import { isSharedDashboardQuery, DashboardQueryEditor } from 'app/plugins/datasource/dashboard';
|
||||
import { expressionDatasource, ExpressionDatasourceID } from 'app/features/expressions/ExpressionDatasource';
|
||||
|
||||
interface Props {
|
||||
panel: PanelModel;
|
||||
@ -97,9 +98,11 @@ export class QueriesTab extends PureComponent<Props, State> {
|
||||
if (datasource.meta.mixed) {
|
||||
// Set the datasource on all targets
|
||||
panel.targets.forEach(target => {
|
||||
target.datasource = panel.datasource;
|
||||
if (!target.datasource) {
|
||||
target.datasource = config.defaultDatasource;
|
||||
if (target.datasource !== ExpressionDatasourceID) {
|
||||
target.datasource = panel.datasource;
|
||||
if (!target.datasource) {
|
||||
target.datasource = config.defaultDatasource;
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (currentDS) {
|
||||
@ -107,7 +110,9 @@ export class QueriesTab extends PureComponent<Props, State> {
|
||||
if (currentDS.meta.mixed) {
|
||||
// Remove the explicit datasource
|
||||
for (const target of panel.targets) {
|
||||
delete target.datasource;
|
||||
if (target.datasource !== ExpressionDatasourceID) {
|
||||
delete target.datasource;
|
||||
}
|
||||
}
|
||||
} else if (currentDS.meta.id !== datasource.meta.id) {
|
||||
// we are changing data source type, clear queries
|
||||
@ -150,6 +155,11 @@ export class QueriesTab extends PureComponent<Props, State> {
|
||||
this.onScrollBottom();
|
||||
};
|
||||
|
||||
onAddExpressionClick = () => {
|
||||
this.onUpdateQueries(addQuery(this.props.panel.targets, expressionDatasource.newQuery()));
|
||||
this.onScrollBottom();
|
||||
};
|
||||
|
||||
onScrollBottom = () => {
|
||||
this.setState({ scrollTop: this.state.scrollTop + 10000 });
|
||||
};
|
||||
@ -168,6 +178,11 @@ export class QueriesTab extends PureComponent<Props, State> {
|
||||
</button>
|
||||
)}
|
||||
{isAddingMixed && this.renderMixedPicker()}
|
||||
{config.featureToggles.expressions && (
|
||||
<button className="btn navbar-button" onClick={this.onAddExpressionClick}>
|
||||
Add Expression
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -14,6 +14,7 @@ import {
|
||||
DataQueryError,
|
||||
} from '@grafana/ui';
|
||||
import { LoadingState, dateMath, toDataFrame, DataFrame, guessFieldTypes } from '@grafana/data';
|
||||
import { ExpressionDatasourceID, expressionDatasource } from 'app/features/expressions/ExpressionDatasource';
|
||||
|
||||
type MapOfResponsePackets = { [str: string]: DataQueryResponse };
|
||||
|
||||
@ -132,6 +133,16 @@ function cancelNetworkRequestsOnUnsubscribe(req: DataQueryRequest) {
|
||||
}
|
||||
|
||||
export function callQueryMethod(datasource: DataSourceApi, request: DataQueryRequest) {
|
||||
console.log('CALL', request.targets);
|
||||
|
||||
// If any query has an expression, use the expression endpoint
|
||||
for (const target of request.targets) {
|
||||
if (target.datasource === ExpressionDatasourceID) {
|
||||
return expressionDatasource.query(request);
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise it is a standard datasource request
|
||||
const returnVal = datasource.query(request);
|
||||
return from(returnVal);
|
||||
}
|
||||
|
84
public/app/features/expressions/ExpressionDatasource.ts
Normal file
84
public/app/features/expressions/ExpressionDatasource.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import {
|
||||
DataSourceApi,
|
||||
DataQueryRequest,
|
||||
DataQueryResponse,
|
||||
DataSourceInstanceSettings,
|
||||
DataSourcePluginMeta,
|
||||
} from '@grafana/ui';
|
||||
import { ExpressionQuery, GELQueryType } from './types';
|
||||
import { ExpressionQueryEditor } from './ExpressionQueryEditor';
|
||||
import { Observable, from } from 'rxjs';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||
import { gelResponseToDataFrames } from './util';
|
||||
|
||||
/**
|
||||
* This is a singleton that is not actually instanciated
|
||||
*/
|
||||
export class ExpressionDatasourceApi extends DataSourceApi<ExpressionQuery> {
|
||||
constructor(instanceSettings: DataSourceInstanceSettings) {
|
||||
super(instanceSettings);
|
||||
}
|
||||
|
||||
getCollapsedText(query: ExpressionQuery) {
|
||||
return `Expression: ${query.type}`;
|
||||
}
|
||||
|
||||
query(request: DataQueryRequest): Observable<DataQueryResponse> {
|
||||
const { targets, intervalMs, maxDataPoints, range } = request;
|
||||
|
||||
const orgId = (window as any).grafanaBootData.user.orgId;
|
||||
const queries = targets.map(q => {
|
||||
if (q.datasource === ExpressionDatasourceID) {
|
||||
return {
|
||||
...q,
|
||||
datasourceId: this.id,
|
||||
orgId,
|
||||
};
|
||||
}
|
||||
const ds = config.datasources[q.datasource || config.defaultDatasource];
|
||||
return {
|
||||
...q,
|
||||
datasourceId: ds.id,
|
||||
intervalMs,
|
||||
maxDataPoints,
|
||||
orgId,
|
||||
// ?? alias: templateSrv.replace(q.alias || ''),
|
||||
};
|
||||
});
|
||||
const req: Promise<DataQueryResponse> = getBackendSrv()
|
||||
.post('/api/tsdb/query/v2', {
|
||||
from: range.from.valueOf().toString(),
|
||||
to: range.to.valueOf().toString(),
|
||||
queries: queries,
|
||||
})
|
||||
.then((rsp: any) => {
|
||||
return { data: gelResponseToDataFrames(rsp) } as DataQueryResponse;
|
||||
});
|
||||
return from(req);
|
||||
}
|
||||
|
||||
testDatasource() {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
|
||||
newQuery(): ExpressionQuery {
|
||||
return {
|
||||
refId: '--', // Replaced with query
|
||||
type: GELQueryType.math,
|
||||
datasource: ExpressionDatasourceID,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const ExpressionDatasourceID = '__expr__';
|
||||
export const expressionDatasource = new ExpressionDatasourceApi({
|
||||
id: -100,
|
||||
name: ExpressionDatasourceID,
|
||||
} as DataSourceInstanceSettings);
|
||||
expressionDatasource.meta = {
|
||||
id: ExpressionDatasourceID,
|
||||
} as DataSourcePluginMeta;
|
||||
expressionDatasource.components = {
|
||||
QueryEditor: ExpressionQueryEditor,
|
||||
};
|
159
public/app/features/expressions/ExpressionQueryEditor.tsx
Normal file
159
public/app/features/expressions/ExpressionQueryEditor.tsx
Normal file
@ -0,0 +1,159 @@
|
||||
// Libraries
|
||||
import React, { PureComponent, ChangeEvent } from 'react';
|
||||
|
||||
import { FormLabel, QueryEditorProps, Select, FormField } from '@grafana/ui';
|
||||
import { SelectableValue, ReducerID } from '@grafana/data';
|
||||
|
||||
// Types
|
||||
import { ExpressionQuery, GELQueryType } from './types';
|
||||
import { ExpressionDatasourceApi } from './ExpressionDatasource';
|
||||
|
||||
type Props = QueryEditorProps<ExpressionDatasourceApi, ExpressionQuery>;
|
||||
|
||||
interface State {}
|
||||
|
||||
const gelTypes: Array<SelectableValue<GELQueryType>> = [
|
||||
{ value: GELQueryType.math, label: 'Math' },
|
||||
{ value: GELQueryType.reduce, label: 'Reduce' },
|
||||
{ value: GELQueryType.resample, label: 'Resample' },
|
||||
];
|
||||
|
||||
const reducerTypes: Array<SelectableValue<string>> = [
|
||||
{ value: ReducerID.min, label: 'Min', description: 'Get the minimum value' },
|
||||
{ value: ReducerID.max, label: 'Max', description: 'Get the maximum value' },
|
||||
{ value: ReducerID.mean, label: 'Mean', description: 'Get the average value' },
|
||||
{ value: ReducerID.sum, label: 'Sum', description: 'Get the sum of all values' },
|
||||
{ value: ReducerID.count, label: 'Count', description: 'Get the number of values' },
|
||||
];
|
||||
|
||||
const downsamplingTypes: Array<SelectableValue<string>> = [
|
||||
{ value: ReducerID.min, label: 'Min', description: 'Fill with the minimum value' },
|
||||
{ value: ReducerID.max, label: 'Max', description: 'Fill with the maximum value' },
|
||||
{ value: ReducerID.mean, label: 'Mean', description: 'Fill with the average value' },
|
||||
{ value: ReducerID.sum, label: 'Sum', description: 'Fill with the sum of all values' },
|
||||
];
|
||||
|
||||
const upsamplingTypes: Array<SelectableValue<string>> = [
|
||||
{ value: 'pad', label: 'pad', description: 'fill with the last known value' },
|
||||
{ value: 'backfilling', label: 'backfilling', description: 'fill with the next known value' },
|
||||
{ value: 'fillna', label: 'fillna', description: 'Fill with NaNs' },
|
||||
];
|
||||
|
||||
export class ExpressionQueryEditor extends PureComponent<Props, State> {
|
||||
state = {};
|
||||
|
||||
onSelectGELType = (item: SelectableValue<GELQueryType>) => {
|
||||
const { query, onChange } = this.props;
|
||||
const q = {
|
||||
...query,
|
||||
type: item.value!,
|
||||
};
|
||||
|
||||
if (q.type === GELQueryType.reduce) {
|
||||
if (!q.reducer) {
|
||||
q.reducer = ReducerID.mean;
|
||||
}
|
||||
q.expression = undefined;
|
||||
} else if (q.type === GELQueryType.resample) {
|
||||
if (!q.downsampler) {
|
||||
q.downsampler = ReducerID.mean;
|
||||
}
|
||||
if (!q.upsampler) {
|
||||
q.upsampler = 'fillna';
|
||||
}
|
||||
q.reducer = undefined;
|
||||
} else {
|
||||
q.reducer = undefined;
|
||||
}
|
||||
|
||||
onChange(q);
|
||||
};
|
||||
|
||||
onSelectReducer = (item: SelectableValue<string>) => {
|
||||
const { query, onChange } = this.props;
|
||||
onChange({
|
||||
...query,
|
||||
reducer: item.value!,
|
||||
});
|
||||
};
|
||||
|
||||
onSelectUpsampler = (item: SelectableValue<string>) => {
|
||||
const { query, onChange } = this.props;
|
||||
onChange({
|
||||
...query,
|
||||
upsampler: item.value!,
|
||||
});
|
||||
};
|
||||
|
||||
onSelectDownsampler = (item: SelectableValue<string>) => {
|
||||
const { query, onChange } = this.props;
|
||||
onChange({
|
||||
...query,
|
||||
downsampler: item.value!,
|
||||
});
|
||||
};
|
||||
|
||||
onRuleReducer = (item: SelectableValue<string>) => {
|
||||
const { query, onChange } = this.props;
|
||||
onChange({
|
||||
...query,
|
||||
rule: item.value!,
|
||||
});
|
||||
};
|
||||
|
||||
onExpressionChange = (evt: ChangeEvent<any>) => {
|
||||
const { query, onChange } = this.props;
|
||||
onChange({
|
||||
...query,
|
||||
expression: evt.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
onRuleChange = (evt: ChangeEvent<any>) => {
|
||||
const { query, onChange } = this.props;
|
||||
onChange({
|
||||
...query,
|
||||
rule: evt.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { query } = this.props;
|
||||
const selected = gelTypes.find(o => o.value === query.type);
|
||||
const reducer = reducerTypes.find(o => o.value === query.reducer);
|
||||
const downsampler = downsamplingTypes.find(o => o.value === query.downsampler);
|
||||
const upsampler = upsamplingTypes.find(o => o.value === query.upsampler);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="form-field">
|
||||
<Select options={gelTypes} value={selected} onChange={this.onSelectGELType} />
|
||||
{query.type === GELQueryType.reduce && (
|
||||
<>
|
||||
<FormLabel width={5}>Function:</FormLabel>
|
||||
<Select options={reducerTypes} value={reducer} onChange={this.onSelectReducer} />
|
||||
<FormField label="Fields:" labelWidth={5} onChange={this.onExpressionChange} value={query.expression} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{query.type === GELQueryType.math && (
|
||||
<textarea value={query.expression} onChange={this.onExpressionChange} className="gf-form-input" rows={2} />
|
||||
)}
|
||||
{query.type === GELQueryType.resample && (
|
||||
<>
|
||||
<div>
|
||||
<FormField label="Series:" labelWidth={5} onChange={this.onExpressionChange} value={query.expression} />
|
||||
<FormField label="Rule:" labelWidth={5} onChange={this.onRuleChange} value={query.rule} />
|
||||
</div>
|
||||
<div>
|
||||
<FormLabel width={12}>Downsample Function:</FormLabel>
|
||||
<Select options={downsamplingTypes} value={downsampler} onChange={this.onSelectDownsampler} />
|
||||
<FormLabel width={12}>Upsample Function:</FormLabel>
|
||||
<Select options={upsamplingTypes} value={upsampler} onChange={this.onSelectUpsampler} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
128
public/app/features/expressions/__snapshots__/util.test.ts.snap
Normal file
128
public/app/features/expressions/__snapshots__/util.test.ts.snap
Normal file
@ -0,0 +1,128 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`GEL Utils should parse output with dataframe 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"fields": Array [
|
||||
Object {
|
||||
"config": Object {},
|
||||
"name": "Time",
|
||||
"type": "time",
|
||||
"values": Int32Array [
|
||||
882710016,
|
||||
365389179,
|
||||
1587742720,
|
||||
365389180,
|
||||
-2002191872,
|
||||
365389181,
|
||||
-1297159168,
|
||||
365389182,
|
||||
-592126464,
|
||||
365389183,
|
||||
112906240,
|
||||
365389185,
|
||||
817938944,
|
||||
365389186,
|
||||
1522971648,
|
||||
365389187,
|
||||
-2066962944,
|
||||
365389188,
|
||||
-1361930240,
|
||||
365389189,
|
||||
-656897536,
|
||||
365389190,
|
||||
48135168,
|
||||
365389192,
|
||||
753167872,
|
||||
365389193,
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"config": Object {},
|
||||
"name": "",
|
||||
"type": "number",
|
||||
"values": Float64Array [
|
||||
3,
|
||||
3,
|
||||
3,
|
||||
5,
|
||||
5,
|
||||
5,
|
||||
3,
|
||||
3,
|
||||
3,
|
||||
5,
|
||||
5,
|
||||
5,
|
||||
3,
|
||||
],
|
||||
},
|
||||
],
|
||||
"labels": undefined,
|
||||
"meta": undefined,
|
||||
"name": undefined,
|
||||
"refId": undefined,
|
||||
},
|
||||
Object {
|
||||
"fields": Array [
|
||||
Object {
|
||||
"config": Object {},
|
||||
"name": "Time",
|
||||
"type": "time",
|
||||
"values": Int32Array [
|
||||
882710016,
|
||||
365389179,
|
||||
1587742720,
|
||||
365389180,
|
||||
-2002191872,
|
||||
365389181,
|
||||
-1297159168,
|
||||
365389182,
|
||||
-592126464,
|
||||
365389183,
|
||||
112906240,
|
||||
365389185,
|
||||
817938944,
|
||||
365389186,
|
||||
1522971648,
|
||||
365389187,
|
||||
-2066962944,
|
||||
365389188,
|
||||
-1361930240,
|
||||
365389189,
|
||||
-656897536,
|
||||
365389190,
|
||||
48135168,
|
||||
365389192,
|
||||
753167872,
|
||||
365389193,
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"config": Object {},
|
||||
"name": "GB-series",
|
||||
"type": "number",
|
||||
"values": Float64Array [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
0,
|
||||
],
|
||||
},
|
||||
],
|
||||
"labels": undefined,
|
||||
"meta": undefined,
|
||||
"name": undefined,
|
||||
"refId": undefined,
|
||||
},
|
||||
]
|
||||
`;
|
20
public/app/features/expressions/types.ts
Normal file
20
public/app/features/expressions/types.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { DataQuery } from '@grafana/ui';
|
||||
|
||||
export enum GELQueryType {
|
||||
math = 'math',
|
||||
reduce = 'reduce',
|
||||
resample = 'resample',
|
||||
}
|
||||
|
||||
/**
|
||||
* For now this is a single object to cover all the types.... would likely
|
||||
* want to split this up by type as the complexity increases
|
||||
*/
|
||||
export interface ExpressionQuery extends DataQuery {
|
||||
type: GELQueryType;
|
||||
reducer?: string;
|
||||
expression?: string;
|
||||
rule?: string;
|
||||
downsampler?: string;
|
||||
upsampler?: string;
|
||||
}
|
48
public/app/features/expressions/util.test.ts
Normal file
48
public/app/features/expressions/util.test.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { gelResponseToDataFrames } from './util';
|
||||
import { toDataFrameDTO } from '@grafana/data';
|
||||
|
||||
/* tslint:disable */
|
||||
const resp = {
|
||||
results: {
|
||||
'': {
|
||||
refId: '',
|
||||
dataframes: [
|
||||
'QVJST1cxAACsAQAAEAAAAAAACgAOAAwACwAEAAoAAAAUAAAAAAAAAQMACgAMAAAACAAEAAoAAAAIAAAAUAAAAAIAAAAoAAAABAAAAOD+//8IAAAADAAAAAIAAABHQwAABQAAAHJlZklkAAAAAP///wgAAAAMAAAAAAAAAAAAAAAEAAAAbmFtZQAAAAACAAAAlAAAAAQAAACG////FAAAAGAAAABgAAAAAAADAWAAAAACAAAALAAAAAQAAABQ////CAAAABAAAAAGAAAAbnVtYmVyAAAEAAAAdHlwZQAAAAB0////CAAAAAwAAAAAAAAAAAAAAAQAAABuYW1lAAAAAAAAAABm////AAACAAAAAAAAABIAGAAUABMAEgAMAAAACAAEABIAAAAUAAAAbAAAAHQAAAAAAAoBdAAAAAIAAAA0AAAABAAAANz///8IAAAAEAAAAAQAAAB0aW1lAAAAAAQAAAB0eXBlAAAAAAgADAAIAAQACAAAAAgAAAAQAAAABAAAAFRpbWUAAAAABAAAAG5hbWUAAAAAAAAAAAAABgAIAAYABgAAAAAAAwAEAAAAVGltZQAAAAC8AAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAA0AAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAWAAAAA0AAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABoAAAAAAAAAGgAAAAAAAAAAAAAAAAAAABoAAAAAAAAAGgAAAAAAAAAAAAAAAIAAAANAAAAAAAAAAAAAAAAAAAADQAAAAAAAAAAAAAAAAAAAAAAAAAAFp00e2XHFQAIo158ZccVAPqoiH1lxxUA7K6yfmXHFQDetNx/ZccVANC6BoFlxxUAwsAwgmXHFQC0xlqDZccVAKbMhIRlxxUAmNKuhWXHFQCK2NiGZccVAHzeAohlxxUAbuQsiWXHFQAAAAAAAAhAAAAAAAAACEAAAAAAAAAIQAAAAAAAABRAAAAAAAAAFEAAAAAAAAAUQAAAAAAAAAhAAAAAAAAACEAAAAAAAAAIQAAAAAAAABRAAAAAAAAAFEAAAAAAAAAUQAAAAAAAAAhAEAAAAAwAFAASAAwACAAEAAwAAAAQAAAALAAAADgAAAAAAAMAAQAAALgBAAAAAAAAwAAAAAAAAADQAAAAAAAAAAAAAAAAAAAAAAAKAAwAAAAIAAQACgAAAAgAAABQAAAAAgAAACgAAAAEAAAA4P7//wgAAAAMAAAAAgAAAEdDAAAFAAAAcmVmSWQAAAAA////CAAAAAwAAAAAAAAAAAAAAAQAAABuYW1lAAAAAAIAAACUAAAABAAAAIb///8UAAAAYAAAAGAAAAAAAAMBYAAAAAIAAAAsAAAABAAAAFD///8IAAAAEAAAAAYAAABudW1iZXIAAAQAAAB0eXBlAAAAAHT///8IAAAADAAAAAAAAAAAAAAABAAAAG5hbWUAAAAAAAAAAGb///8AAAIAAAAAAAAAEgAYABQAEwASAAwAAAAIAAQAEgAAABQAAABsAAAAdAAAAAAACgF0AAAAAgAAADQAAAAEAAAA3P///wgAAAAQAAAABAAAAHRpbWUAAAAABAAAAHR5cGUAAAAACAAMAAgABAAIAAAACAAAABAAAAAEAAAAVGltZQAAAAAEAAAAbmFtZQAAAAAAAAAAAAAGAAgABgAGAAAAAAADAAQAAABUaW1lAAAAANgBAABBUlJPVzE=',
|
||||
'QVJST1cxAAC8AQAAEAAAAAAACgAOAAwACwAEAAoAAAAUAAAAAAAAAQMACgAMAAAACAAEAAoAAAAIAAAAUAAAAAIAAAAoAAAABAAAAND+//8IAAAADAAAAAIAAABHQgAABQAAAHJlZklkAAAA8P7//wgAAAAMAAAAAAAAAAAAAAAEAAAAbmFtZQAAAAACAAAApAAAAAQAAAB2////FAAAAGgAAABoAAAAAAADAWgAAAACAAAALAAAAAQAAABA////CAAAABAAAAAGAAAAbnVtYmVyAAAEAAAAdHlwZQAAAABk////CAAAABQAAAAJAAAAR0Itc2VyaWVzAAAABAAAAG5hbWUAAAAAAAAAAF7///8AAAIACQAAAEdCLXNlcmllcwASABgAFAATABIADAAAAAgABAASAAAAFAAAAGwAAAB0AAAAAAAKAXQAAAACAAAANAAAAAQAAADc////CAAAABAAAAAEAAAAdGltZQAAAAAEAAAAdHlwZQAAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAvAAAABQAAAAAAAAADAAWABQAEwAMAAQADAAAANAAAAAAAAAAFAAAAAAAAAMDAAoAGAAMAAgABAAKAAAAFAAAAFgAAAANAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaAAAAAAAAABoAAAAAAAAAAAAAAAAAAAAaAAAAAAAAABoAAAAAAAAAAAAAAACAAAADQAAAAAAAAAAAAAAAAAAAA0AAAAAAAAAAAAAAAAAAAAAAAAAABadNHtlxxUACKNefGXHFQD6qIh9ZccVAOyusn5lxxUA3rTcf2XHFQDQugaBZccVAMLAMIJlxxUAtMZag2XHFQCmzISEZccVAJjSroVlxxUAitjYhmXHFQB83gKIZccVAG7kLIllxxUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAABAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAABAAAAAAAAAAEAAAAAAAAAAABAAAAAMABQAEgAMAAgABAAMAAAAEAAAACwAAAA4AAAAAAADAAEAAADIAQAAAAAAAMAAAAAAAAAA0AAAAAAAAAAAAAAAAAAAAAAACgAMAAAACAAEAAoAAAAIAAAAUAAAAAIAAAAoAAAABAAAAND+//8IAAAADAAAAAIAAABHQgAABQAAAHJlZklkAAAA8P7//wgAAAAMAAAAAAAAAAAAAAAEAAAAbmFtZQAAAAACAAAApAAAAAQAAAB2////FAAAAGgAAABoAAAAAAADAWgAAAACAAAALAAAAAQAAABA////CAAAABAAAAAGAAAAbnVtYmVyAAAEAAAAdHlwZQAAAABk////CAAAABQAAAAJAAAAR0Itc2VyaWVzAAAABAAAAG5hbWUAAAAAAAAAAF7///8AAAIACQAAAEdCLXNlcmllcwASABgAFAATABIADAAAAAgABAASAAAAFAAAAGwAAAB0AAAAAAAKAXQAAAACAAAANAAAAAQAAADc////CAAAABAAAAAEAAAAdGltZQAAAAAEAAAAdHlwZQAAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAA6AEAAEFSUk9XMQ==',
|
||||
],
|
||||
series: [] as any[],
|
||||
tables: null as any,
|
||||
frames: null as any,
|
||||
},
|
||||
},
|
||||
};
|
||||
/* tslint:enable */
|
||||
|
||||
describe('GEL Utils', () => {
|
||||
// test('should parse sample GEL output', () => {
|
||||
// const frames = gelResponseToDataFrames(resp);
|
||||
// const frame = frames[0];
|
||||
// expect(frame.name).toEqual('BBB');
|
||||
// expect(frame.fields.length).toEqual(2);
|
||||
// expect(frame.length).toEqual(resp.Frames[0].fields[0].values.length);
|
||||
|
||||
// const timeField = frame.fields[0];
|
||||
// expect(timeField.name).toEqual('Time');
|
||||
|
||||
// // The whole response
|
||||
// expect(frames).toMatchSnapshot();
|
||||
// });
|
||||
|
||||
test('should parse output with dataframe', () => {
|
||||
const frames = gelResponseToDataFrames(resp);
|
||||
for (const frame of frames) {
|
||||
console.log('Frame', frame.refId + ' // ' + frame.labels);
|
||||
for (const field of frame.fields) {
|
||||
console.log(' > ', field.name, field.values.toArray());
|
||||
}
|
||||
}
|
||||
|
||||
const norm = frames.map(f => toDataFrameDTO(f));
|
||||
expect(norm).toMatchSnapshot();
|
||||
});
|
||||
});
|
62
public/app/features/expressions/util.ts
Normal file
62
public/app/features/expressions/util.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { DataFrame, FieldType, Field, Vector } from '@grafana/data';
|
||||
import { Table, ArrowType } from 'apache-arrow';
|
||||
|
||||
export function base64StringToArrowTable(text: string) {
|
||||
const b64 = atob(text);
|
||||
const arr = Uint8Array.from(b64, c => {
|
||||
return c.charCodeAt(0);
|
||||
});
|
||||
return Table.from(arr);
|
||||
}
|
||||
|
||||
export function arrowTableToDataFrame(table: Table): DataFrame {
|
||||
const fields: Field[] = [];
|
||||
for (let i = 0; i < table.numCols; i++) {
|
||||
const col = table.getColumnAt(i);
|
||||
if (col) {
|
||||
const schema = table.schema.fields[i];
|
||||
let type = FieldType.other;
|
||||
const values: Vector<any> = col;
|
||||
switch ((schema.typeId as unknown) as ArrowType) {
|
||||
case ArrowType.Decimal:
|
||||
case ArrowType.Int:
|
||||
case ArrowType.FloatingPoint: {
|
||||
type = FieldType.number;
|
||||
break;
|
||||
}
|
||||
case ArrowType.Bool: {
|
||||
type = FieldType.boolean;
|
||||
break;
|
||||
}
|
||||
case ArrowType.Timestamp: {
|
||||
type = FieldType.time;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.log('UNKNOWN Type:', schema);
|
||||
}
|
||||
|
||||
fields.push({
|
||||
name: col.name,
|
||||
type,
|
||||
config: {}, // TODO, pull from metadata
|
||||
values,
|
||||
});
|
||||
}
|
||||
}
|
||||
return {
|
||||
fields,
|
||||
length: table.length,
|
||||
};
|
||||
}
|
||||
|
||||
export function gelResponseToDataFrames(rsp: any): DataFrame[] {
|
||||
const frames: DataFrame[] = [];
|
||||
for (const res of Object.values(rsp.results)) {
|
||||
for (const b of (res as any).dataframes) {
|
||||
const t = base64StringToArrowTable(b as string);
|
||||
frames.push(arrowTableToDataFrame(t));
|
||||
}
|
||||
}
|
||||
return frames;
|
||||
}
|
@ -14,6 +14,9 @@ import { auto } from 'angular';
|
||||
import { TemplateSrv } from '../templating/template_srv';
|
||||
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
|
||||
|
||||
// Pretend Datasource
|
||||
import { expressionDatasource } from 'app/features/expressions/ExpressionDatasource';
|
||||
|
||||
export class DatasourceSrv implements DataSourceService {
|
||||
datasources: { [name: string]: DataSourceApi };
|
||||
|
||||
@ -56,6 +59,12 @@ export class DatasourceSrv implements DataSourceService {
|
||||
}
|
||||
|
||||
loadDatasource(name: string): Promise<DataSourceApi> {
|
||||
// Expression Datasource (not a real datasource)
|
||||
if (name === expressionDatasource.name) {
|
||||
this.datasources[name] = expressionDatasource;
|
||||
return this.$q.when(expressionDatasource);
|
||||
}
|
||||
|
||||
const dsConfig = config.datasources[name];
|
||||
if (!dsConfig) {
|
||||
return this.$q.reject({ message: 'Datasource named ' + name + ' was not found' });
|
||||
|
143
yarn.lock
143
yarn.lock
@ -3266,6 +3266,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/file-saver/-/file-saver-2.0.1.tgz#e18eb8b069e442f7b956d313f4fadd3ef887354e"
|
||||
integrity sha512-g1QUuhYVVAamfCifK7oB7G3aIl4BbOyzDOqVyUfEr4tfBKrXfeH+M+Tg7HKCXSrbzxYdhyCP7z9WbKo0R2hBCw==
|
||||
|
||||
"@types/flatbuffers@^1.9.1":
|
||||
version "1.9.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/flatbuffers/-/flatbuffers-1.9.1.tgz#1910bebfc15c8f67a287fae07bfc061f94e9d291"
|
||||
integrity sha512-TC3X0Nkj5wgvuY217VkodBtjbD3Yr0JNApDY1GW9IU5Mzm5ie1IJErqe4vRm+wy08IRz3bemaDATrdEw1CJlVQ==
|
||||
|
||||
"@types/geojson@*":
|
||||
version "7946.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.7.tgz#c8fa532b60a0042219cdf173ca21a975ef0666ad"
|
||||
@ -3791,6 +3796,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.4.tgz#b4ffc7dc97b498c969b360a41eee247f82616370"
|
||||
integrity sha512-78AdXtlhpCHT0K3EytMpn4JNxaf5tbqbLcbIRoQIHzpTIyjpxLQKRoxU55ujBXAtg3Nl2h/XWvfDa9dsMOd0pQ==
|
||||
|
||||
"@types/text-encoding-utf-8@^1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/text-encoding-utf-8/-/text-encoding-utf-8-1.0.1.tgz#908d884af1114e5d8df47597b1e04f833383d23d"
|
||||
integrity sha512-GpIEYaS+yNfYqpowLLziiY42pyaL+lThd/wMh6tTubaKuG4IRkXqqyxK7Nddn3BvpUg2+go3Gv/jbXvAFMRjiQ==
|
||||
|
||||
"@types/through@*":
|
||||
version "0.0.29"
|
||||
resolved "https://registry.yarnpkg.com/@types/through/-/through-0.0.29.tgz#72943aac922e179339c651fa34a4428a4d722f93"
|
||||
@ -4400,6 +4410,22 @@ anymatch@^2.0.0:
|
||||
micromatch "^3.1.4"
|
||||
normalize-path "^2.1.1"
|
||||
|
||||
apache-arrow@0.15.0:
|
||||
version "0.15.0"
|
||||
resolved "https://registry.yarnpkg.com/apache-arrow/-/apache-arrow-0.15.0.tgz#aa34ba635c5e73579566be4d25008c412dae31b9"
|
||||
integrity sha512-TLF7Bq7hPolQgFUsKVvYk2Qq7axg8k8yQ4J1uCLitkiUotdYACqN/j3ZE03cVJvSnXRKmqS2TCIjN4h7rOJKEQ==
|
||||
dependencies:
|
||||
"@types/flatbuffers" "^1.9.1"
|
||||
"@types/node" "^12.0.4"
|
||||
"@types/text-encoding-utf-8" "^1.0.1"
|
||||
command-line-args "5.0.2"
|
||||
command-line-usage "5.0.5"
|
||||
flatbuffers "1.11.0"
|
||||
json-bignum "^0.0.3"
|
||||
pad-left "^2.1.0"
|
||||
text-encoding-utf-8 "^1.0.2"
|
||||
tslib "^1.9.3"
|
||||
|
||||
app-root-dir@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/app-root-dir/-/app-root-dir-1.0.2.tgz#38187ec2dea7577fff033ffcb12172692ff6e118"
|
||||
@ -4467,6 +4493,14 @@ argparse@^1.0.2, argparse@^1.0.7:
|
||||
dependencies:
|
||||
sprintf-js "~1.0.2"
|
||||
|
||||
argv-tools@^0.1.1:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/argv-tools/-/argv-tools-0.1.2.tgz#fc4918a70775b8cc5f8296fa0cfea137bd8a8229"
|
||||
integrity sha512-wxqoymY0BEu9NblZVQiOTOAiJUjPhaa/kbNMjC2h6bnrmUSgnxKgWJo3lzXvi3bHJRwXyqK/dHzMlZVRT89Cxg==
|
||||
dependencies:
|
||||
array-back "^2.0.0"
|
||||
find-replace "^2.0.1"
|
||||
|
||||
arr-diff@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf"
|
||||
@ -4489,6 +4523,13 @@ arr-union@^3.1.0:
|
||||
resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4"
|
||||
integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=
|
||||
|
||||
array-back@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/array-back/-/array-back-2.0.0.tgz#6877471d51ecc9c9bfa6136fb6c7d5fe69748022"
|
||||
integrity sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==
|
||||
dependencies:
|
||||
typical "^2.6.1"
|
||||
|
||||
array-differ@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031"
|
||||
@ -6307,6 +6348,27 @@ command-exists@^1.2.8:
|
||||
resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.8.tgz#715acefdd1223b9c9b37110a149c6392c2852291"
|
||||
integrity sha512-PM54PkseWbiiD/mMsbvW351/u+dafwTJ0ye2qB60G1aGQP9j3xK2gmMDc+R34L3nDtx4qMCitXT75mkbkGJDLw==
|
||||
|
||||
command-line-args@5.0.2:
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-5.0.2.tgz#c4e56b016636af1323cf485aa25c3cb203dfbbe4"
|
||||
integrity sha512-/qPcbL8zpqg53x4rAaqMFlRV4opN3pbla7I7k9x8kyOBMQoGT6WltjN6sXZuxOXw6DgdK7Ad+ijYS5gjcr7vlA==
|
||||
dependencies:
|
||||
argv-tools "^0.1.1"
|
||||
array-back "^2.0.0"
|
||||
find-replace "^2.0.1"
|
||||
lodash.camelcase "^4.3.0"
|
||||
typical "^2.6.1"
|
||||
|
||||
command-line-usage@5.0.5:
|
||||
version "5.0.5"
|
||||
resolved "https://registry.yarnpkg.com/command-line-usage/-/command-line-usage-5.0.5.tgz#5f25933ffe6dedd983c635d38a21d7e623fda357"
|
||||
integrity sha512-d8NrGylA5oCXSbGoKz05FkehDAzSmIm4K03S5VDh4d5lZAtTWfc3D1RuETtuQCn8129nYfJfDdF7P/lwcz1BlA==
|
||||
dependencies:
|
||||
array-back "^2.0.0"
|
||||
chalk "^2.4.1"
|
||||
table-layout "^0.4.3"
|
||||
typical "^2.6.1"
|
||||
|
||||
commander@2, commander@^2.12.1, commander@^2.14.1, commander@^2.18.0, commander@^2.19.0, commander@^2.20.0, commander@^2.5.0, commander@^2.8.1, commander@^2.9.0:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
@ -7780,7 +7842,7 @@ deep-equal@~1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
|
||||
integrity sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=
|
||||
|
||||
deep-extend@^0.6.0:
|
||||
deep-extend@^0.6.0, deep-extend@~0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
|
||||
integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
|
||||
@ -9285,6 +9347,14 @@ find-parent-dir@^0.3.0:
|
||||
resolved "https://registry.yarnpkg.com/find-parent-dir/-/find-parent-dir-0.3.0.tgz#33c44b429ab2b2f0646299c5f9f718f376ff8d54"
|
||||
integrity sha1-M8RLQpqysvBkYpnF+fcY83b/jVQ=
|
||||
|
||||
find-replace@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-2.0.1.tgz#6d9683a7ca20f8f9aabeabad07e4e2580f528550"
|
||||
integrity sha512-LzDo3Fpa30FLIBsh6DCDnMN1KW2g4QKkqKmejlImgWY67dDFPX/x9Kh/op/GK522DchQXEvDi/wD48HKW49XOQ==
|
||||
dependencies:
|
||||
array-back "^2.0.0"
|
||||
test-value "^3.0.0"
|
||||
|
||||
find-root@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4"
|
||||
@ -9347,6 +9417,11 @@ flat-cache@^1.2.1:
|
||||
rimraf "~2.6.2"
|
||||
write "^0.2.1"
|
||||
|
||||
flatbuffers@1.11.0:
|
||||
version "1.11.0"
|
||||
resolved "https://registry.yarnpkg.com/flatbuffers/-/flatbuffers-1.11.0.tgz#90a47e584dd7851ad7a913f5a0ee99c1d76ce59f"
|
||||
integrity sha512-0PqFKtXI4MjxomI7jO4g5XfLPm/15g2R+5WGCHBGYGh0ihQiypnHlJ6bMmkkrAe0GzZ4d7PDAfCONKIPUxNF+A==
|
||||
|
||||
flatten@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782"
|
||||
@ -12260,6 +12335,11 @@ jsesc@^2.5.1:
|
||||
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
|
||||
integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
|
||||
|
||||
json-bignum@^0.0.3:
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/json-bignum/-/json-bignum-0.0.3.tgz#41163b50436c773d82424dbc20ed70db7604b8d7"
|
||||
integrity sha1-QRY7UENsdz2CQk28IO1w23YEuNc=
|
||||
|
||||
json-parse-better-errors@^1.0.0, json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
|
||||
@ -12905,6 +12985,11 @@ lodash._root@~3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692"
|
||||
integrity sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=
|
||||
|
||||
lodash.camelcase@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
|
||||
integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY=
|
||||
|
||||
lodash.capitalize@^4.1.0:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz#f826c9b4e2a8511d84e3aca29db05e1a4f3b72a9"
|
||||
@ -12984,6 +13069,11 @@ lodash.once@^4.1.1:
|
||||
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
|
||||
integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
|
||||
|
||||
lodash.padend@^4.6.1:
|
||||
version "4.6.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.padend/-/lodash.padend-4.6.1.tgz#53ccba047d06e158d311f45da625f4e49e6f166e"
|
||||
integrity sha1-U8y6BH0G4VjTEfRdpiX05J5vFm4=
|
||||
|
||||
lodash.set@^4.3.2:
|
||||
version "4.3.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23"
|
||||
@ -15060,6 +15150,13 @@ pacote@^9.1.0, pacote@^9.2.3, pacote@^9.5.0:
|
||||
unique-filename "^1.1.1"
|
||||
which "^1.3.1"
|
||||
|
||||
pad-left@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/pad-left/-/pad-left-2.1.0.tgz#16e6a3b2d44a8e138cb0838cc7cb403a4fc9e994"
|
||||
integrity sha1-FuajstRKjhOMsIOMx8tAOk/J6ZQ=
|
||||
dependencies:
|
||||
repeat-string "^1.5.4"
|
||||
|
||||
pako@~1.0.5:
|
||||
version "1.0.10"
|
||||
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.10.tgz#4328badb5086a426aa90f541977d4955da5c9732"
|
||||
@ -17534,6 +17631,11 @@ redent@^2.0.0:
|
||||
indent-string "^3.0.0"
|
||||
strip-indent "^2.0.0"
|
||||
|
||||
reduce-flatten@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-1.0.1.tgz#258c78efd153ddf93cb561237f61184f3696e327"
|
||||
integrity sha1-JYx479FT3fk8tWEjf2EYTzaW4yc=
|
||||
|
||||
redux-logger@3.0.6:
|
||||
version "3.0.6"
|
||||
resolved "https://registry.yarnpkg.com/redux-logger/-/redux-logger-3.0.6.tgz#f7555966f3098f3c88604c449cf0baf5778274bf"
|
||||
@ -19592,6 +19694,17 @@ systemjs@0.20.19:
|
||||
resolved "https://registry.yarnpkg.com/systemjs/-/systemjs-0.20.19.tgz#c2b9e79c19f4bea53a19b1ed3f974ffb463be949"
|
||||
integrity sha512-H/rKwNEEyej/+IhkmFNmKFyJul8tbH/muiPq5TyNoVTwsGhUjRsN3NlFnFQUvFXA3+GQmsXkCNXU6QKPl779aw==
|
||||
|
||||
table-layout@^0.4.3:
|
||||
version "0.4.5"
|
||||
resolved "https://registry.yarnpkg.com/table-layout/-/table-layout-0.4.5.tgz#d906de6a25fa09c0c90d1d08ecd833ecedcb7378"
|
||||
integrity sha512-zTvf0mcggrGeTe/2jJ6ECkJHAQPIYEwDoqsiqBjI24mvRmQbInK5jq33fyypaCBxX08hMkfmdOqj6haT33EqWw==
|
||||
dependencies:
|
||||
array-back "^2.0.0"
|
||||
deep-extend "~0.6.0"
|
||||
lodash.padend "^4.6.1"
|
||||
typical "^2.6.1"
|
||||
wordwrapjs "^3.0.0"
|
||||
|
||||
table@^3.7.8:
|
||||
version "3.8.3"
|
||||
resolved "https://registry.yarnpkg.com/table/-/table-3.8.3.tgz#2bbc542f0fda9861a755d3947fefd8b3f513855f"
|
||||
@ -19768,6 +19881,14 @@ test-exclude@^5.2.3:
|
||||
read-pkg-up "^4.0.0"
|
||||
require-main-filename "^2.0.0"
|
||||
|
||||
test-value@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/test-value/-/test-value-3.0.0.tgz#9168c062fab11a86b8d444dd968bb4b73851ce92"
|
||||
integrity sha512-sVACdAWcZkSU9x7AOmJo5TqE+GyNJknHaHsMrR6ZnhjVlVN9Yx6FjHrsKZ3BjIpPCT68zYesPWkakrNupwfOTQ==
|
||||
dependencies:
|
||||
array-back "^2.0.0"
|
||||
typical "^2.6.1"
|
||||
|
||||
"tether-drop@https://github.com/torkelo/drop/tarball/master":
|
||||
version "1.5.0"
|
||||
resolved "https://github.com/torkelo/drop/tarball/master#6a3eb15b882b416f06e1e7ae04c7e57d08418020"
|
||||
@ -19784,6 +19905,11 @@ tether@^1.1.0:
|
||||
resolved "https://registry.yarnpkg.com/tether/-/tether-1.4.7.tgz#d56a818590d8fe72e387f77a67f93ab96d8e1fb2"
|
||||
integrity sha512-Z0J1aExjoFU8pybVkQAo/vD2wfSO63r+XOPfWQMC5qtf1bI7IWqNk4MiyBcgvvnY8kqnY06dVdvwTK2S3PU/Fw==
|
||||
|
||||
text-encoding-utf-8@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz#585b62197b0ae437e3c7b5d0af27ac1021e10d13"
|
||||
integrity sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==
|
||||
|
||||
text-extensions@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-2.0.0.tgz#43eabd1b495482fae4a2bf65e5f56c29f69220f6"
|
||||
@ -20143,7 +20269,7 @@ ts-pnp@^1.1.2:
|
||||
resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.1.4.tgz#ae27126960ebaefb874c6d7fa4729729ab200d90"
|
||||
integrity sha512-1J/vefLC+BWSo+qe8OnJQfWTYRS6ingxjwqmHMqaMxXMj7kFtKLgAaYW3JeX3mktjgUL+etlU8/B4VUAUI9QGw==
|
||||
|
||||
tslib@1.10.0, tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0:
|
||||
tslib@1.10.0, tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
|
||||
integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
|
||||
@ -20281,6 +20407,11 @@ typescript@3.6.3:
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.3.tgz#fea942fabb20f7e1ca7164ff626f1a9f3f70b4da"
|
||||
integrity sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw==
|
||||
|
||||
typical@^2.6.1:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/typical/-/typical-2.6.1.tgz#5c080e5d661cbbe38259d2e70a3c7253e873881d"
|
||||
integrity sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0=
|
||||
|
||||
ua-parser-js@^0.7.18, ua-parser-js@^0.7.9:
|
||||
version "0.7.20"
|
||||
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.20.tgz#7527178b82f6a62a0f243d1f94fd30e3e3c21098"
|
||||
@ -21255,6 +21386,14 @@ wordwrap@~1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
|
||||
integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=
|
||||
|
||||
wordwrapjs@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wordwrapjs/-/wordwrapjs-3.0.0.tgz#c94c372894cadc6feb1a66bff64e1d9af92c5d1e"
|
||||
integrity sha512-mO8XtqyPvykVCsrwj5MlOVWvSnCdT+C+QVbm6blradR7JExAhbkZ7hZ9A+9NUtwzSqrlUo9a67ws0EiILrvRpw==
|
||||
dependencies:
|
||||
reduce-flatten "^1.0.1"
|
||||
typical "^2.6.1"
|
||||
|
||||
worker-farm@^1.5.2, worker-farm@^1.6.0, worker-farm@^1.7.0:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8"
|
||||
|
Loading…
Reference in New Issue
Block a user