Commit 861eb721 by Ryan McKinley Committed by Kyle Brandt

transform: add expressions to query editor (w/ feature flag) (#20072)

for use with gel which is not released yet.
parent 2bb46847
......@@ -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 err != nil {
return Error(400, "GEL datasource missing ID", nil)
}
if name == "-- GEL --" {
break
if name == "__expr__" {
expr = true
}
datasourceID, err := query.Get("datasourceId").Int64()
if err != nil {
return Error(500, "datasource missing ID", nil)
}
ds, err := hs.DatasourceCache.GetDatasource(datasourceID, c.SignedInUser, c.SkipCache)
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 := &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),
......@@ -59,12 +63,22 @@ func (hs *HTTPServer) QueryMetricsV2(c *m.ReqContext, reqDto dtos.MetricRequest)
Model: query,
DataSource: ds,
})
}
resp, err := tsdb.HandleRequest(c.Req.Context(), ds, request)
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
for _, res := range resp.Results {
......
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,18 +98,22 @@ export class QueriesTab extends PureComponent<Props, State> {
if (datasource.meta.mixed) {
// Set the datasource on all targets
panel.targets.forEach(target => {
if (target.datasource !== ExpressionDatasourceID) {
target.datasource = panel.datasource;
if (!target.datasource) {
target.datasource = config.defaultDatasource;
}
}
});
} else if (currentDS) {
// if switching from mixed
if (currentDS.meta.mixed) {
// Remove the explicit datasource
for (const target of panel.targets) {
if (target.datasource !== ExpressionDatasourceID) {
delete target.datasource;
}
}
} else if (currentDS.meta.id !== datasource.meta.id) {
// we are changing data source type, clear queries
panel.targets = [{ refId: 'A' }];
......@@ -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);
}
......
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,
};
// 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>
);
}
}
// 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,
},
]
`;
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;
}
import { gelResponseToDataFrames } from './util';
import { toDataFrameDTO } from '@grafana/data';
/* tslint:disable */
const resp = {
results: {
'': {
refId: '',
dataframes: [
'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',
],
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();
});
});
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' });
......
......@@ -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"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment