mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
Add inspector for VEGA (#70941)
* [WIP] Add inspector for VEGA Closes: #31189 * view -> dataset * cleanup * add spec viewer * cleanup code * use rx to retrieve data from adapters * Make custom inspector adapters registerable from the visType * fix flex-box size * cleanup * remove visTypesWithoutInspector from visualize_embeddable * fix PR comments * add vega folder to sass-lint * fix jest * Update src/plugins/vis_type_vega/public/vega_inspector/components/data_viewer.tsx Co-authored-by: Wylie Conlon <wylieconlon@gmail.com> * use addSignalListener * cleanup * add onColumnResize handler * EuiCodeEditor -> CodeEditor * fix type_check * fix issue with pagination * fix extra vertical scroll * add area-label for EuiButtonIcon * add area-label for EuiComboBox * Design Commit - Fixing up layout trying to remove any `.eui` classes and uses flex instead of percentage - Fixing text to use `Sentence case` not `Title Case` * Wrapper around signal viewer table * fix Jest snapshot Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> Co-authored-by: Wylie Conlon <wylieconlon@gmail.com> Co-authored-by: cchaos <caroline.horn@elastic.co>
This commit is contained in:
parent
81cbd13db4
commit
e1ffcccb96
56 changed files with 993 additions and 84 deletions
|
@ -3,6 +3,7 @@ files:
|
|||
- 'src/legacy/core_plugins/metrics/**/*.s+(a|c)ss'
|
||||
- 'src/plugins/timelion/**/*.s+(a|c)ss'
|
||||
- 'src/plugins/vis_type_vislib/**/*.s+(a|c)ss'
|
||||
- 'src/plugins/vis_type_vega/**/*.s+(a|c)ss'
|
||||
- 'src/plugins/vis_type_xy/**/*.s+(a|c)ss'
|
||||
- 'x-pack/plugins/canvas/**/*.s+(a|c)ss'
|
||||
- 'x-pack/plugins/triggers_actions_ui/**/*.s+(a|c)ss'
|
||||
|
|
|
@ -52,7 +52,6 @@ import { EuiButtonEmptyProps } from '@elastic/eui';
|
|||
import { EuiComboBoxProps } from '@elastic/eui';
|
||||
import { EuiConfirmModalProps } from '@elastic/eui';
|
||||
import { EuiGlobalToastListToast } from '@elastic/eui';
|
||||
import { EventEmitter } from 'events';
|
||||
import { ExclusiveUnion } from '@elastic/eui';
|
||||
import { ExistsParams } from 'elasticsearch';
|
||||
import { ExplainParams } from 'elasticsearch';
|
||||
|
@ -148,7 +147,7 @@ import { ReindexRethrottleParams } from 'elasticsearch';
|
|||
import { RenderSearchTemplateParams } from 'elasticsearch';
|
||||
import { Reporter } from '@kbn/analytics';
|
||||
import { RequestAdapter } from 'src/plugins/inspector/common';
|
||||
import { RequestStatistics as RequestStatistics_2 } from 'src/plugins/inspector/common';
|
||||
import { RequestStatistics } from 'src/plugins/inspector/common';
|
||||
import { Required } from '@kbn/utility-types';
|
||||
import * as Rx from 'rxjs';
|
||||
import { SavedObject } from 'src/core/server';
|
||||
|
|
|
@ -129,7 +129,7 @@ export const getTermsBucketAgg = () =>
|
|||
|
||||
const response = await nestedSearchSource.fetch({ abortSignal });
|
||||
request
|
||||
.stats(getResponseInspectorStats(nestedSearchSource, response))
|
||||
.stats(getResponseInspectorStats(response, nestedSearchSource))
|
||||
.ok({ json: response });
|
||||
resp = mergeOtherBucketAggResponse(aggConfigs, resp, response, aggConfig, filterAgg());
|
||||
}
|
||||
|
|
|
@ -160,7 +160,7 @@ const handleCourierRequest = async ({
|
|||
|
||||
(searchSource as any).lastQuery = queryHash;
|
||||
|
||||
request.stats(getResponseInspectorStats(searchSource, response)).ok({ json: response });
|
||||
request.stats(getResponseInspectorStats(response, searchSource)).ok({ json: response });
|
||||
|
||||
(searchSource as any).rawResponse = response;
|
||||
} catch (e) {
|
||||
|
|
|
@ -61,10 +61,11 @@ export function getRequestInspectorStats(searchSource: ISearchSource) {
|
|||
|
||||
/** @public */
|
||||
export function getResponseInspectorStats(
|
||||
searchSource: ISearchSource,
|
||||
resp: SearchResponse<unknown>
|
||||
resp: SearchResponse<unknown>,
|
||||
searchSource?: ISearchSource
|
||||
) {
|
||||
const lastRequest = searchSource.history && searchSource.history[searchSource.history.length - 1];
|
||||
const lastRequest =
|
||||
searchSource?.history && searchSource.history[searchSource.history.length - 1];
|
||||
const stats: RequestStatistics = {};
|
||||
|
||||
if (resp && resp.took) {
|
||||
|
|
|
@ -39,7 +39,6 @@ import { DeleteTemplateParams } from 'elasticsearch';
|
|||
import { DetailedPeerCertificate } from 'tls';
|
||||
import { Duration } from 'moment';
|
||||
import { ErrorToastOptions } from 'src/core/public/notifications';
|
||||
import { EventEmitter } from 'events';
|
||||
import { ExistsParams } from 'elasticsearch';
|
||||
import { ExplainParams } from 'elasticsearch';
|
||||
import { FieldStatsParams } from 'elasticsearch';
|
||||
|
|
|
@ -874,7 +874,7 @@ function discoverController(
|
|||
}
|
||||
|
||||
function onResults(resp) {
|
||||
inspectorRequest.stats(getResponseInspectorStats($scope.searchSource, resp)).ok({ json: resp });
|
||||
inspectorRequest.stats(getResponseInspectorStats(resp, $scope.searchSource)).ok({ json: resp });
|
||||
|
||||
if (getTimeField()) {
|
||||
const tabifiedData = tabifyAggResponse($scope.vis.data.aggs, resp);
|
||||
|
|
|
@ -307,7 +307,7 @@ export class SearchEmbeddable extends Embeddable<SearchInput, SearchOutput>
|
|||
this.updateOutput({ loading: false, error: undefined });
|
||||
|
||||
// Log response to inspector
|
||||
inspectorRequest.stats(getResponseInspectorStats(searchSource, resp)).ok({ json: resp });
|
||||
inspectorRequest.stats(getResponseInspectorStats(resp, searchSource)).ok({ json: resp });
|
||||
|
||||
// Apply the changes to the angular scope
|
||||
this.searchScope.$apply(() => {
|
||||
|
|
|
@ -23,7 +23,7 @@ import { createExecutionContainer, ExecutionContainer } from './container';
|
|||
import { createError } from '../util';
|
||||
import { Defer, now } from '../../../kibana_utils/common';
|
||||
import { toPromise } from '../../../data/common/utils/abort_utils';
|
||||
import { RequestAdapter, DataAdapter } from '../../../inspector/common';
|
||||
import { RequestAdapter, DataAdapter, Adapters } from '../../../inspector/common';
|
||||
import { isExpressionValueError, ExpressionValueError } from '../expression_types/specs/error';
|
||||
import {
|
||||
ExpressionAstExpression,
|
||||
|
@ -70,7 +70,7 @@ export class Execution<
|
|||
ExtraContext extends Record<string, unknown> = Record<string, unknown>,
|
||||
Input = unknown,
|
||||
Output = unknown,
|
||||
InspectorAdapters = ExtraContext['inspectorAdapters'] extends object
|
||||
InspectorAdapters extends Adapters = ExtraContext['inspectorAdapters'] extends object
|
||||
? ExtraContext['inspectorAdapters']
|
||||
: DefaultInspectorAdapters
|
||||
> {
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
|
||||
import { ExpressionType } from '../expression_types';
|
||||
import { DataAdapter, RequestAdapter } from '../../../inspector/common';
|
||||
import { Adapters, DataAdapter, RequestAdapter } from '../../../inspector/common';
|
||||
import { TimeRange, Query, Filter } from '../../../data/common';
|
||||
import { SavedObject, SavedObjectAttributes } from '../../../../core/public';
|
||||
|
||||
|
@ -26,7 +26,7 @@ import { SavedObject, SavedObjectAttributes } from '../../../../core/public';
|
|||
* `ExecutionContext` is an object available to all functions during a single execution;
|
||||
* it provides various methods to perform side-effects.
|
||||
*/
|
||||
export interface ExecutionContext<Input = unknown, InspectorAdapters = DefaultInspectorAdapters> {
|
||||
export interface ExecutionContext<Input = unknown, InspectorAdapters extends Adapters = Adapters> {
|
||||
/**
|
||||
* Get initial input with which execution started.
|
||||
*/
|
||||
|
@ -75,7 +75,7 @@ export interface ExecutionContext<Input = unknown, InspectorAdapters = DefaultIn
|
|||
/**
|
||||
* Default inspector adapters created if inspector adapters are not set explicitly.
|
||||
*/
|
||||
export interface DefaultInspectorAdapters {
|
||||
export interface DefaultInspectorAdapters extends Adapters {
|
||||
requests: RequestAdapter;
|
||||
data: DataAdapter;
|
||||
}
|
||||
|
|
|
@ -150,7 +150,7 @@ export class ExpressionLoader {
|
|||
variables: params.variables || {},
|
||||
inspectorAdapters: params.inspectorAdapters,
|
||||
});
|
||||
if (!params.inspectorAdapters) params.inspectorAdapters = this.execution.inspect() as Adapters;
|
||||
|
||||
const prevDataHandler = this.execution;
|
||||
const data = await prevDataHandler.getData();
|
||||
if (this.execution !== prevDataHandler) {
|
||||
|
@ -181,6 +181,9 @@ export class ExpressionLoader {
|
|||
if (params.variables && this.params) {
|
||||
this.params.variables = params.variables;
|
||||
}
|
||||
|
||||
this.params.inspectorAdapters = (params.inspectorAdapters ||
|
||||
this.execution?.inspect()) as Adapters;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -65,6 +65,7 @@ export function createInputControlVisTypeDefinition(deps: InputControlVisDepende
|
|||
},
|
||||
],
|
||||
},
|
||||
inspectorAdapters: {},
|
||||
requestHandler: 'none',
|
||||
responseHandler: 'none',
|
||||
};
|
||||
|
|
|
@ -17,5 +17,12 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export { Adapters } from './types';
|
||||
export { DataAdapter, FormattedData } from './data';
|
||||
export { RequestAdapter, RequestStatistic, RequestStatistics, RequestStatus } from './request';
|
||||
export {
|
||||
RequestAdapter,
|
||||
RequestStatistic,
|
||||
RequestStatistics,
|
||||
RequestStatus,
|
||||
RequestResponder,
|
||||
} from './request';
|
||||
|
|
|
@ -19,3 +19,4 @@
|
|||
|
||||
export { RequestStatistic, RequestStatistics, RequestStatus } from './types';
|
||||
export { RequestAdapter } from './request_adapter';
|
||||
export { RequestResponder } from './request_responder';
|
||||
|
|
25
src/plugins/inspector/common/adapters/types.ts
Normal file
25
src/plugins/inspector/common/adapters/types.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The interface that the adapters used to open an inspector have to fullfill.
|
||||
*/
|
||||
export interface Adapters {
|
||||
[key: string]: any;
|
||||
}
|
|
@ -1 +1 @@
|
|||
@import 'views/index'
|
||||
@import 'views/index';
|
||||
|
|
|
@ -22,8 +22,9 @@ import * as React from 'react';
|
|||
import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../core/public';
|
||||
import { toMountPoint } from '../../kibana_react/public';
|
||||
import { InspectorViewRegistry } from './view_registry';
|
||||
import { Adapters, InspectorOptions, InspectorSession } from './types';
|
||||
import { InspectorOptions, InspectorSession } from './types';
|
||||
import { InspectorPanel } from './ui/inspector_panel';
|
||||
import { Adapters } from '../common';
|
||||
|
||||
import { getRequestsViewDescription, getDataViewDescription } from './views';
|
||||
|
||||
|
|
|
@ -18,23 +18,17 @@
|
|||
*/
|
||||
|
||||
import { OverlayRef } from '../../../core/public';
|
||||
|
||||
/**
|
||||
* The interface that the adapters used to open an inspector have to fullfill.
|
||||
*/
|
||||
export interface Adapters {
|
||||
[key: string]: any;
|
||||
}
|
||||
import { Adapters } from '../common';
|
||||
|
||||
/**
|
||||
* The props interface that a custom inspector view component, that will be passed
|
||||
* to {@link InspectorViewDescription#component}, must use.
|
||||
*/
|
||||
export interface InspectorViewProps {
|
||||
export interface InspectorViewProps<TAdapters extends Adapters = Adapters> {
|
||||
/**
|
||||
* Adapters used to open the inspector.
|
||||
*/
|
||||
adapters: Adapters;
|
||||
adapters: TAdapters;
|
||||
/**
|
||||
* The title that the inspector is currently using e.g. a visualization name.
|
||||
*/
|
||||
|
|
|
@ -306,9 +306,11 @@ exports[`InspectorPanel should render as expected 1`] = `
|
|||
</EuiFlexGroup>
|
||||
</div>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>
|
||||
<EuiFlyoutBody
|
||||
className="insInspectorPanel__flyoutBody"
|
||||
>
|
||||
<div
|
||||
className="euiFlyoutBody"
|
||||
className="euiFlyoutBody insInspectorPanel__flyoutBody"
|
||||
>
|
||||
<div
|
||||
className="euiFlyoutBody__overflow"
|
||||
|
|
12
src/plugins/inspector/public/ui/inspector_panel.scss
Normal file
12
src/plugins/inspector/public/ui/inspector_panel.scss
Normal file
|
@ -0,0 +1,12 @@
|
|||
.insInspectorPanel__flyoutBody {
|
||||
// TODO: EUI to allow for custom classNames to inner elements
|
||||
// Or supply this as default
|
||||
> div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> div {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,7 +20,8 @@
|
|||
import React from 'react';
|
||||
import { mountWithIntl } from 'test_utils/enzyme_helpers';
|
||||
import { InspectorPanel } from './inspector_panel';
|
||||
import { Adapters, InspectorViewDescription } from '../types';
|
||||
import { InspectorViewDescription } from '../types';
|
||||
import { Adapters } from '../../common';
|
||||
|
||||
describe('InspectorPanel', () => {
|
||||
let adapters: Adapters;
|
||||
|
|
|
@ -17,11 +17,13 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import './inspector_panel.scss';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiFlyoutHeader, EuiTitle, EuiFlyoutBody } from '@elastic/eui';
|
||||
import { Adapters, InspectorViewDescription } from '../types';
|
||||
import { InspectorViewDescription } from '../types';
|
||||
import { Adapters } from '../../common';
|
||||
import { InspectorViewChooser } from './inspector_view_chooser';
|
||||
|
||||
function hasAdaptersChanged(oldAdapters: Adapters, newAdapters: Adapters) {
|
||||
|
@ -122,7 +124,9 @@ export class InspectorPanel extends Component<InspectorPanelProps, InspectorPane
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>{this.renderSelectedPanel()}</EuiFlyoutBody>
|
||||
<EuiFlyoutBody className="insInspectorPanel__flyoutBody">
|
||||
{this.renderSelectedPanel()}
|
||||
</EuiFlyoutBody>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
import { InspectorViewRegistry } from './view_registry';
|
||||
import { InspectorViewDescription } from './types';
|
||||
|
||||
import { Adapters } from './types';
|
||||
import { Adapters } from '../common';
|
||||
|
||||
function createMockView(
|
||||
params: {
|
||||
|
|
|
@ -18,7 +18,8 @@
|
|||
*/
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import { Adapters, InspectorViewDescription } from './types';
|
||||
import { InspectorViewDescription } from './types';
|
||||
import { Adapters } from '../common';
|
||||
|
||||
/**
|
||||
* @callback viewShouldShowFunc
|
||||
|
|
|
@ -30,7 +30,8 @@ import {
|
|||
} from '@elastic/eui';
|
||||
|
||||
import { DataTableFormat } from './data_table';
|
||||
import { InspectorViewProps, Adapters } from '../../../types';
|
||||
import { InspectorViewProps } from '../../../types';
|
||||
import { Adapters } from '../../../../common';
|
||||
import {
|
||||
TabularLoaderOptions,
|
||||
TabularData,
|
||||
|
|
|
@ -20,7 +20,8 @@ import React from 'react';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { DataViewComponent } from './components/data_view';
|
||||
import { Adapters, InspectorViewDescription, InspectorViewProps } from '../../types';
|
||||
import { InspectorViewDescription, InspectorViewProps } from '../../types';
|
||||
import { Adapters } from '../../../common';
|
||||
import { IUiSettingsClient } from '../../../../../core/public';
|
||||
|
||||
export const getDataViewDescription = (
|
||||
|
|
|
@ -19,7 +19,8 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { RequestsViewComponent } from './components/requests_view';
|
||||
import { Adapters, InspectorViewDescription } from '../../types';
|
||||
import { InspectorViewDescription } from '../../types';
|
||||
import { Adapters } from '../../../common';
|
||||
|
||||
export const getRequestsViewDescription = (): InspectorViewDescription => ({
|
||||
title: i18n.translate('inspector.requests.requestsTitle', {
|
||||
|
|
|
@ -66,4 +66,5 @@ export const markdownVisDefinition = {
|
|||
},
|
||||
requestHandler: 'none',
|
||||
responseHandler: 'none',
|
||||
inspectorAdapters: {},
|
||||
};
|
||||
|
|
|
@ -62,6 +62,7 @@ export function getTimelionVisDefinition(dependencies: TimelionVisDependencies)
|
|||
},
|
||||
requestHandler: timelionRequestHandler,
|
||||
responseHandler: 'none',
|
||||
inspectorAdapters: {},
|
||||
options: {
|
||||
showIndexSelection: false,
|
||||
showQueryBar: false,
|
||||
|
|
|
@ -78,5 +78,6 @@ export const metricsVisDefinition = {
|
|||
showIndexSelection: false,
|
||||
},
|
||||
requestHandler: metricsRequestHandler,
|
||||
inspectorAdapters: {},
|
||||
responseHandler: 'none',
|
||||
};
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"version": "kibana",
|
||||
"server": true,
|
||||
"ui": true,
|
||||
"requiredPlugins": ["data", "visualizations", "mapsLegacy", "expressions"],
|
||||
"requiredPlugins": ["data", "visualizations", "mapsLegacy", "expressions", "inspector"],
|
||||
"requiredBundles": ["kibanaUtils", "kibanaReact"]
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
// BUG #23514: Make sure Vega doesn't display the controls in two places
|
||||
.vega-bindings {
|
||||
// sass-lint:disable no-important
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
@ -47,7 +48,7 @@
|
|||
width: $euiSizeM * 10 - $euiSize;
|
||||
}
|
||||
|
||||
input[type="range"] {
|
||||
input[type='range'] {
|
||||
width: $euiSizeM * 10;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
|
@ -74,7 +75,7 @@
|
|||
top: 0;
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
opacity: 0.8;
|
||||
opacity: .8;
|
||||
z-index: 1;
|
||||
list-style: none;
|
||||
}
|
||||
|
@ -115,25 +116,30 @@
|
|||
@include euiTextTruncate;
|
||||
padding-top: $euiSizeXS;
|
||||
padding-bottom: $euiSizeXS;
|
||||
}
|
||||
|
||||
td.key {
|
||||
color: $euiColorMediumShade;
|
||||
max-width: $euiSize * 10;
|
||||
text-align: right;
|
||||
padding-right: $euiSizeXS;
|
||||
}
|
||||
td.value {
|
||||
max-width: $euiSizeL * 10;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: map-get($euiBreakpoints, 'm')){
|
||||
td.key {
|
||||
max-width: $euiSize * 6;
|
||||
}
|
||||
td.value {
|
||||
&.key {
|
||||
color: $euiColorMediumShade;
|
||||
max-width: $euiSize * 10;
|
||||
text-align: right;
|
||||
padding-right: $euiSizeXS;
|
||||
}
|
||||
|
||||
&.value {
|
||||
max-width: $euiSizeL * 10;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media only screen and (max-width: map-get($euiBreakpoints, 'm')) {
|
||||
td {
|
||||
&.key {
|
||||
max-width: $euiSize * 6;
|
||||
}
|
||||
|
||||
&.value {
|
||||
max-width: $euiSize * 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,13 +18,17 @@
|
|||
*/
|
||||
|
||||
import { combineLatest } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { map, tap } from 'rxjs/operators';
|
||||
import { CoreStart, IUiSettingsClient } from 'kibana/public';
|
||||
import {
|
||||
getSearchParamsFromRequest,
|
||||
SearchRequest,
|
||||
DataPublicPluginStart,
|
||||
IEsSearchResponse,
|
||||
} from '../../../data/public';
|
||||
import { search as dataPluginSearch } from '../../../data/public';
|
||||
import { VegaInspectorAdapters } from '../vega_inspector';
|
||||
import { RequestResponder } from '../../../inspector/public';
|
||||
|
||||
export interface SearchAPIDependencies {
|
||||
uiSettings: IUiSettingsClient;
|
||||
|
@ -35,26 +39,52 @@ export interface SearchAPIDependencies {
|
|||
export class SearchAPI {
|
||||
constructor(
|
||||
private readonly dependencies: SearchAPIDependencies,
|
||||
private readonly abortSignal?: AbortSignal
|
||||
private readonly abortSignal?: AbortSignal,
|
||||
public readonly inspectorAdapters?: VegaInspectorAdapters
|
||||
) {}
|
||||
|
||||
search(searchRequests: SearchRequest[]) {
|
||||
const { search } = this.dependencies.search;
|
||||
const requestResponders: any = {};
|
||||
|
||||
return combineLatest(
|
||||
searchRequests.map((request, index) => {
|
||||
const requestId: number = index;
|
||||
const params = getSearchParamsFromRequest(request, {
|
||||
uiSettings: this.dependencies.uiSettings,
|
||||
injectedMetadata: this.dependencies.injectedMetadata,
|
||||
});
|
||||
|
||||
if (this.inspectorAdapters) {
|
||||
requestResponders[requestId] = this.inspectorAdapters.requests.start(
|
||||
`#${requestId}`,
|
||||
request
|
||||
);
|
||||
requestResponders[requestId].json(params.body);
|
||||
}
|
||||
|
||||
return search({ params }, { signal: this.abortSignal }).pipe(
|
||||
tap((data) => this.inspectSearchResult(data, requestResponders[requestId])),
|
||||
map((data) => ({
|
||||
id: index,
|
||||
id: requestId,
|
||||
rawResponse: data.rawResponse,
|
||||
}))
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public resetSearchStats() {
|
||||
if (this.inspectorAdapters) {
|
||||
this.inspectorAdapters.requests.reset();
|
||||
}
|
||||
}
|
||||
|
||||
private inspectSearchResult(response: IEsSearchResponse, requestResponder: RequestResponder) {
|
||||
if (requestResponder) {
|
||||
requestResponder
|
||||
.stats(dataPluginSearch.getResponseInspectorStats(response.rawResponse))
|
||||
.ok({ json: response.rawResponse });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,6 +97,7 @@ describe('VegaParser._resolveEsQueries', () => {
|
|||
search: jest.fn(() => ({
|
||||
toPromise: jest.fn(() => Promise.resolve(data)),
|
||||
})),
|
||||
resetSearchStats: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -79,6 +79,7 @@ export class VegaParser {
|
|||
paddingHeight?: number;
|
||||
containerDir?: ControlsLocation | ControlsDirection;
|
||||
controlsDir?: ControlsLocation;
|
||||
searchAPI: SearchAPI;
|
||||
|
||||
constructor(
|
||||
spec: VegaSpec | string,
|
||||
|
@ -92,10 +93,11 @@ export class VegaParser {
|
|||
|
||||
this.error = undefined;
|
||||
this.warnings = [];
|
||||
this.searchAPI = searchAPI;
|
||||
|
||||
const onWarn = this._onWarning.bind(this);
|
||||
this._urlParsers = {
|
||||
elasticsearch: new EsQueryParser(timeCache, searchAPI, filters, onWarn),
|
||||
elasticsearch: new EsQueryParser(timeCache, this.searchAPI, filters, onWarn),
|
||||
emsfile: new EmsFileParser(serviceSettings),
|
||||
url: new UrlParser(onWarn),
|
||||
};
|
||||
|
@ -541,6 +543,8 @@ export class VegaParser {
|
|||
async _resolveDataUrls() {
|
||||
const pending: PendingType = {};
|
||||
|
||||
this.searchAPI.resetSearchStats();
|
||||
|
||||
this._findObjectDataUrls(this.spec!, (obj: Data) => {
|
||||
const url = obj.url;
|
||||
delete obj.url;
|
||||
|
|
|
@ -18,8 +18,10 @@
|
|||
*/
|
||||
import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../core/public';
|
||||
import { Plugin as ExpressionsPublicPlugin } from '../../expressions/public';
|
||||
import { Plugin as DataPublicPlugin } from '../../data/public';
|
||||
import { DataPublicPluginSetup, DataPublicPluginStart } from '../../data/public';
|
||||
import { VisualizationsSetup } from '../../visualizations/public';
|
||||
import { Setup as InspectorSetup } from '../../inspector/public';
|
||||
|
||||
import {
|
||||
setNotifications,
|
||||
setData,
|
||||
|
@ -37,11 +39,13 @@ import { IServiceSettings } from '../../maps_legacy/public';
|
|||
import './index.scss';
|
||||
import { ConfigSchema } from '../config';
|
||||
|
||||
import { getVegaInspectorView } from './vega_inspector';
|
||||
|
||||
/** @internal */
|
||||
export interface VegaVisualizationDependencies {
|
||||
core: CoreSetup;
|
||||
plugins: {
|
||||
data: ReturnType<DataPublicPlugin['setup']>;
|
||||
data: DataPublicPluginSetup;
|
||||
};
|
||||
serviceSettings: IServiceSettings;
|
||||
}
|
||||
|
@ -50,13 +54,14 @@ export interface VegaVisualizationDependencies {
|
|||
export interface VegaPluginSetupDependencies {
|
||||
expressions: ReturnType<ExpressionsPublicPlugin['setup']>;
|
||||
visualizations: VisualizationsSetup;
|
||||
data: ReturnType<DataPublicPlugin['setup']>;
|
||||
inspector: InspectorSetup;
|
||||
data: DataPublicPluginSetup;
|
||||
mapsLegacy: any;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface VegaPluginStartDependencies {
|
||||
data: ReturnType<DataPublicPlugin['start']>;
|
||||
data: DataPublicPluginStart;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
|
@ -69,7 +74,7 @@ export class VegaPlugin implements Plugin<Promise<void>, void> {
|
|||
|
||||
public async setup(
|
||||
core: CoreSetup,
|
||||
{ data, expressions, visualizations, mapsLegacy }: VegaPluginSetupDependencies
|
||||
{ inspector, data, expressions, visualizations, mapsLegacy }: VegaPluginSetupDependencies
|
||||
) {
|
||||
setInjectedVars({
|
||||
enableExternalUrls: this.initializerContext.config.get().enableExternalUrls,
|
||||
|
@ -88,6 +93,8 @@ export class VegaPlugin implements Plugin<Promise<void>, void> {
|
|||
serviceSettings: mapsLegacy.serviceSettings,
|
||||
};
|
||||
|
||||
inspector.registerView(getVegaInspectorView({ uiSettings: core.uiSettings }));
|
||||
|
||||
expressions.registerFunction(() => createVegaFn(visualizationDependencies));
|
||||
|
||||
visualizations.createBaseVisualization(createVegaTypeDefinition(visualizationDependencies));
|
||||
|
|
|
@ -19,9 +19,15 @@
|
|||
|
||||
import { get } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ExpressionFunctionDefinition, KibanaContext, Render } from '../../expressions/public';
|
||||
import {
|
||||
ExecutionContext,
|
||||
ExpressionFunctionDefinition,
|
||||
KibanaContext,
|
||||
Render,
|
||||
} from '../../expressions/public';
|
||||
import { VegaVisualizationDependencies } from './plugin';
|
||||
import { createVegaRequestHandler } from './vega_request_handler';
|
||||
import { VegaInspectorAdapters } from './vega_inspector/index';
|
||||
import { TimeRange, Query } from '../../data/public';
|
||||
import { VegaParser } from './data_model/vega_parser';
|
||||
|
||||
|
@ -42,7 +48,13 @@ interface RenderValue {
|
|||
|
||||
export const createVegaFn = (
|
||||
dependencies: VegaVisualizationDependencies
|
||||
): ExpressionFunctionDefinition<'vega', Input, Arguments, Output> => ({
|
||||
): ExpressionFunctionDefinition<
|
||||
'vega',
|
||||
Input,
|
||||
Arguments,
|
||||
Output,
|
||||
ExecutionContext<unknown, VegaInspectorAdapters>
|
||||
> => ({
|
||||
name: 'vega',
|
||||
type: 'render',
|
||||
inputTypes: ['kibana_context', 'null'],
|
||||
|
@ -57,7 +69,7 @@ export const createVegaFn = (
|
|||
},
|
||||
},
|
||||
async fn(input, args, context) {
|
||||
const vegaRequestHandler = createVegaRequestHandler(dependencies, context.abortSignal);
|
||||
const vegaRequestHandler = createVegaRequestHandler(dependencies, context);
|
||||
|
||||
const response = await vegaRequestHandler({
|
||||
timeRange: get(input, 'timeRange') as TimeRange,
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React, { useState, useCallback, useEffect } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiComboBox,
|
||||
EuiFlexGroup,
|
||||
EuiComboBoxProps,
|
||||
EuiFlexItem,
|
||||
EuiSpacer,
|
||||
CommonProps,
|
||||
} from '@elastic/eui';
|
||||
import { VegaAdapter, InspectDataSets } from '../vega_adapter';
|
||||
import { InspectorDataGrid } from './inspector_data_grid';
|
||||
|
||||
interface DataViewerProps extends CommonProps {
|
||||
vegaAdapter: VegaAdapter;
|
||||
}
|
||||
|
||||
const getDataGridArialabel = (view: InspectDataSets) =>
|
||||
i18n.translate('visTypeVega.inspector.dataViewer.gridAriaLabel', {
|
||||
defaultMessage: '{name} data grid',
|
||||
values: {
|
||||
name: view.id,
|
||||
},
|
||||
});
|
||||
|
||||
const dataSetAriaLabel = i18n.translate('visTypeVega.inspector.dataViewer.dataSetAriaLabel', {
|
||||
defaultMessage: 'Data set',
|
||||
});
|
||||
|
||||
export const DataViewer = ({ vegaAdapter, ...rest }: DataViewerProps) => {
|
||||
const [inspectDataSets, setInspectDataSets] = useState<InspectDataSets[]>([]);
|
||||
const [selectedView, setSelectedView] = useState<InspectDataSets>();
|
||||
const [dataGridAriaLabel, setDataGridAriaLabel] = useState<string>('');
|
||||
|
||||
const onViewChange: EuiComboBoxProps<unknown>['onChange'] = useCallback(
|
||||
(selectedOptions) => {
|
||||
const newView = inspectDataSets!.find((view) => view.id === selectedOptions[0].label);
|
||||
|
||||
if (newView) {
|
||||
setDataGridAriaLabel(getDataGridArialabel(newView));
|
||||
setSelectedView(newView);
|
||||
}
|
||||
},
|
||||
[inspectDataSets]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = vegaAdapter.getDataSetsSubscription().subscribe((dataSets) => {
|
||||
setInspectDataSets(dataSets);
|
||||
});
|
||||
|
||||
return () => {
|
||||
subscription.unsubscribe();
|
||||
};
|
||||
}, [vegaAdapter]);
|
||||
|
||||
useEffect(() => {
|
||||
if (inspectDataSets) {
|
||||
if (!selectedView) {
|
||||
setSelectedView(inspectDataSets[0]);
|
||||
} else {
|
||||
setDataGridAriaLabel(getDataGridArialabel(selectedView));
|
||||
}
|
||||
}
|
||||
}, [selectedView, inspectDataSets]);
|
||||
|
||||
if (!selectedView) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="s" wrap={false} responsive={false} {...rest}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiComboBox
|
||||
fullWidth
|
||||
options={inspectDataSets.map((item: any) => ({
|
||||
label: item.id,
|
||||
}))}
|
||||
aria-label={dataSetAriaLabel}
|
||||
onChange={onViewChange}
|
||||
isClearable={false}
|
||||
singleSelection={{ asPlainText: true }}
|
||||
selectedOptions={[{ label: selectedView.id }]}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={true}>
|
||||
<InspectorDataGrid
|
||||
columns={selectedView.columns}
|
||||
data={selectedView.data}
|
||||
dataGridAriaLabel={dataGridAriaLabel}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export { DataViewer } from './data_viewer';
|
||||
export { SignalViewer } from './signal_viewer';
|
||||
export { SpecViewer } from './spec_viewer';
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React, { useState, useCallback, useMemo, useEffect } from 'react';
|
||||
import { EuiDataGrid, EuiDataGridSorting, EuiDataGridProps } from '@elastic/eui';
|
||||
import { VegaRuntimeData } from '../vega_adapter';
|
||||
|
||||
const DEFAULT_PAGE_SIZE = 15;
|
||||
|
||||
interface InspectorDataGridProps extends VegaRuntimeData {
|
||||
dataGridAriaLabel: string;
|
||||
}
|
||||
|
||||
export const InspectorDataGrid = ({ columns, data, dataGridAriaLabel }: InspectorDataGridProps) => {
|
||||
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: DEFAULT_PAGE_SIZE });
|
||||
const onChangeItemsPerPage = useCallback(
|
||||
(pageSize) => setPagination((p) => ({ ...p, pageSize, pageIndex: 0 })),
|
||||
[setPagination]
|
||||
);
|
||||
|
||||
const onChangePage = useCallback((pageIndex) => setPagination((p) => ({ ...p, pageIndex })), [
|
||||
setPagination,
|
||||
]);
|
||||
|
||||
// Column visibility
|
||||
const [visibleColumns, setVisibleColumns] = useState<string[]>([]);
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
setPagination({
|
||||
...pagination,
|
||||
pageIndex: 0,
|
||||
});
|
||||
setVisibleColumns(columns.map((column) => column.id));
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[dataGridAriaLabel]
|
||||
);
|
||||
|
||||
// Sorting
|
||||
const [sortingColumns, setSortingColumns] = useState<EuiDataGridSorting['columns']>([]);
|
||||
|
||||
const onSort = useCallback(
|
||||
(newSortingColumns: EuiDataGridSorting['columns']) => {
|
||||
setSortingColumns(newSortingColumns);
|
||||
},
|
||||
[setSortingColumns]
|
||||
);
|
||||
|
||||
let gridData = useMemo(() => {
|
||||
return [...data].sort((a, b) => {
|
||||
for (let i = 0; i < sortingColumns.length; i++) {
|
||||
const column = sortingColumns[i];
|
||||
const aValue = a[column.id];
|
||||
const bValue = b[column.id];
|
||||
|
||||
if (aValue < bValue) return column.direction === 'asc' ? -1 : 1;
|
||||
if (aValue > bValue) return column.direction === 'asc' ? 1 : -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}, [data, sortingColumns]);
|
||||
|
||||
const renderCellValue = useMemo(() => {
|
||||
return (({ rowIndex, columnId }) => {
|
||||
let adjustedRowIndex = rowIndex;
|
||||
|
||||
// If we are doing the pagination (instead of leaving that to the grid)
|
||||
// then the row index must be adjusted as `data` has already been pruned to the page size
|
||||
adjustedRowIndex = rowIndex - pagination.pageIndex * pagination.pageSize;
|
||||
|
||||
return gridData.hasOwnProperty(adjustedRowIndex)
|
||||
? gridData[adjustedRowIndex][columnId] || null
|
||||
: null;
|
||||
}) as EuiDataGridProps['renderCellValue'];
|
||||
}, [gridData, pagination.pageIndex, pagination.pageSize]);
|
||||
|
||||
// Pagination
|
||||
gridData = useMemo(() => {
|
||||
const rowStart = pagination.pageIndex * pagination.pageSize;
|
||||
const rowEnd = Math.min(rowStart + pagination.pageSize, gridData.length);
|
||||
return gridData.slice(rowStart, rowEnd);
|
||||
}, [gridData, pagination]);
|
||||
|
||||
// Resize
|
||||
const [columnsWidth, setColumnsWidth] = useState<Record<string, number>>({});
|
||||
|
||||
const onColumnResize: EuiDataGridProps['onColumnResize'] = useCallback(
|
||||
({ columnId, width }) => {
|
||||
setColumnsWidth({
|
||||
...columnsWidth,
|
||||
[columnId]: width,
|
||||
});
|
||||
},
|
||||
[columnsWidth]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiDataGrid
|
||||
aria-label={dataGridAriaLabel}
|
||||
columns={columns.map((column) => {
|
||||
if (columnsWidth[column.id]) {
|
||||
return {
|
||||
...column,
|
||||
initialWidth: columnsWidth[column.id],
|
||||
};
|
||||
}
|
||||
return column;
|
||||
})}
|
||||
columnVisibility={{
|
||||
visibleColumns,
|
||||
setVisibleColumns,
|
||||
}}
|
||||
rowCount={data.length}
|
||||
renderCellValue={renderCellValue}
|
||||
sorting={{ columns: sortingColumns, onSort }}
|
||||
toolbarVisibility={{
|
||||
showFullScreenSelector: false,
|
||||
}}
|
||||
onColumnResize={onColumnResize}
|
||||
pagination={{
|
||||
...pagination,
|
||||
pageSizeOptions: [DEFAULT_PAGE_SIZE, 25, 50],
|
||||
onChangeItemsPerPage,
|
||||
onChangePage,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { VegaAdapter, InspectSignalsSets } from '../vega_adapter';
|
||||
import { InspectorDataGrid } from './inspector_data_grid';
|
||||
|
||||
interface SignalViewerProps {
|
||||
vegaAdapter: VegaAdapter;
|
||||
}
|
||||
|
||||
const initialSignalColumnWidth = 150;
|
||||
|
||||
const signalDataGridAriaLabel = i18n.translate('visTypeVega.inspector.signalViewer.gridAriaLabel', {
|
||||
defaultMessage: 'Signal values data grid',
|
||||
});
|
||||
|
||||
export const SignalViewer = ({ vegaAdapter }: SignalViewerProps) => {
|
||||
const [inspectSignalsSets, setInspectSignalsSets] = useState<InspectSignalsSets>();
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = vegaAdapter.getSignalsSetsSubscription().subscribe((signalSets) => {
|
||||
if (signalSets) {
|
||||
setInspectSignalsSets(signalSets);
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
subscription.unsubscribe();
|
||||
};
|
||||
}, [vegaAdapter]);
|
||||
|
||||
if (!inspectSignalsSets) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<EuiSpacer size="s" />
|
||||
<InspectorDataGrid
|
||||
columns={inspectSignalsSets.columns.map((column, index) => {
|
||||
if (index === 0) {
|
||||
return {
|
||||
...column,
|
||||
initialWidth: initialSignalColumnWidth,
|
||||
};
|
||||
}
|
||||
return column;
|
||||
})}
|
||||
data={inspectSignalsSets.data}
|
||||
dataGridAriaLabel={signalDataGridAriaLabel}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import {
|
||||
EuiFlexItem,
|
||||
EuiFlexGroup,
|
||||
EuiCopy,
|
||||
EuiButtonEmpty,
|
||||
EuiSpacer,
|
||||
CommonProps,
|
||||
} from '@elastic/eui';
|
||||
import { VegaAdapter } from '../vega_adapter';
|
||||
import { CodeEditor } from '../../../../kibana_react/public';
|
||||
|
||||
interface SpecViewerProps extends CommonProps {
|
||||
vegaAdapter: VegaAdapter;
|
||||
}
|
||||
|
||||
const copyToClipboardLabel = i18n.translate(
|
||||
'visTypeVega.inspector.specViewer.copyToClipboardLabel',
|
||||
{
|
||||
defaultMessage: 'Copy to clipboard',
|
||||
}
|
||||
);
|
||||
|
||||
export const SpecViewer = ({ vegaAdapter, ...rest }: SpecViewerProps) => {
|
||||
const [spec, setSpec] = useState<string>();
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = vegaAdapter.getSpecSubscription().subscribe((data) => {
|
||||
if (data) {
|
||||
setSpec(data);
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
subscription.unsubscribe();
|
||||
};
|
||||
}, [vegaAdapter]);
|
||||
|
||||
if (!spec) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="s" wrap={false} responsive={false} {...rest}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSpacer size="s" />
|
||||
<div className="eui-textRight">
|
||||
<EuiCopy textToCopy={spec}>
|
||||
{(copy) => (
|
||||
<EuiButtonEmpty size="xs" flush="right" iconType="copyClipboard" onClick={copy}>
|
||||
{copyToClipboardLabel}
|
||||
</EuiButtonEmpty>
|
||||
)}
|
||||
</EuiCopy>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={true}>
|
||||
<CodeEditor
|
||||
languageId="json"
|
||||
value={spec}
|
||||
onChange={() => {}}
|
||||
options={{
|
||||
readOnly: true,
|
||||
lineNumbers: 'off',
|
||||
fontSize: 12,
|
||||
minimap: {
|
||||
enabled: false,
|
||||
},
|
||||
scrollBeyondLastLine: false,
|
||||
wordWrap: 'on',
|
||||
wrappingIndent: 'indent',
|
||||
automaticLayout: true,
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
24
src/plugins/vis_type_vega/public/vega_inspector/index.ts
Normal file
24
src/plugins/vis_type_vega/public/vega_inspector/index.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export {
|
||||
createInspectorAdapters,
|
||||
getVegaInspectorView,
|
||||
VegaInspectorAdapters,
|
||||
} from './vega_inspector';
|
148
src/plugins/vis_type_vega/public/vega_inspector/vega_adapter.ts
Normal file
148
src/plugins/vis_type_vega/public/vega_inspector/vega_adapter.ts
Normal file
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { Observable, ReplaySubject, fromEventPattern, merge, timer } from 'rxjs';
|
||||
import { map, switchMap, filter, debounce } from 'rxjs/operators';
|
||||
import { View, Runtime, Spec } from 'vega';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Assign } from '@kbn/utility-types';
|
||||
|
||||
interface DebugValues {
|
||||
view: View;
|
||||
spec: Spec;
|
||||
}
|
||||
|
||||
export interface VegaRuntimeData {
|
||||
columns: Array<{
|
||||
id: string;
|
||||
}>;
|
||||
data: Array<Record<string, string>>;
|
||||
}
|
||||
|
||||
export type InspectDataSets = Assign<VegaRuntimeData, { id: string }>;
|
||||
export type InspectSignalsSets = VegaRuntimeData;
|
||||
|
||||
const vegaAdapterSignalLabel = i18n.translate('visTypeVega.inspector.vegaAdapter.signal', {
|
||||
defaultMessage: 'Signal',
|
||||
});
|
||||
|
||||
const vegaAdapterValueLabel = i18n.translate('visTypeVega.inspector.vegaAdapter.value', {
|
||||
defaultMessage: 'Value',
|
||||
});
|
||||
|
||||
/** Get Runtime Scope for Vega View
|
||||
* @link https://vega.github.io/vega/docs/api/debugging/#scope
|
||||
**/
|
||||
const getVegaRuntimeScope = (debugValues: DebugValues) =>
|
||||
(debugValues.view as any)._runtime as Runtime;
|
||||
|
||||
const serializeColumns = (item: Record<string, unknown>, columns: string[]) => {
|
||||
const nonSerializableFieldLabel = '(..)';
|
||||
|
||||
return columns.reduce((row: Record<string, string>, column) => {
|
||||
try {
|
||||
const cell = item[column];
|
||||
row[column] = typeof cell === 'object' ? JSON.stringify(cell) : `${cell}`;
|
||||
} catch (e) {
|
||||
row[column] = nonSerializableFieldLabel;
|
||||
}
|
||||
return row;
|
||||
}, {});
|
||||
};
|
||||
|
||||
export class VegaAdapter {
|
||||
private debugValuesSubject = new ReplaySubject<DebugValues>();
|
||||
|
||||
bindInspectValues(debugValues: DebugValues) {
|
||||
this.debugValuesSubject.next(debugValues);
|
||||
}
|
||||
|
||||
getDataSetsSubscription(): Observable<InspectDataSets[]> {
|
||||
return this.debugValuesSubject.pipe(
|
||||
filter((debugValues) => Boolean(debugValues)),
|
||||
map((debugValues) => {
|
||||
const runtimeScope = getVegaRuntimeScope(debugValues);
|
||||
|
||||
return Object.keys(runtimeScope.data || []).reduce((acc: InspectDataSets[], key) => {
|
||||
const value = runtimeScope.data[key].values.value;
|
||||
|
||||
if (value && value[0]) {
|
||||
const columns = Object.keys(value[0]);
|
||||
acc.push({
|
||||
id: key,
|
||||
columns: columns.map((column) => ({ id: column, schema: 'json' })),
|
||||
data: value.map((item: Record<string, unknown>) => serializeColumns(item, columns)),
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
getSignalsSetsSubscription(): Observable<InspectSignalsSets> {
|
||||
const signalsListener = this.debugValuesSubject.pipe(
|
||||
filter((debugValues) => Boolean(debugValues)),
|
||||
switchMap((debugValues) => {
|
||||
const runtimeScope = getVegaRuntimeScope(debugValues);
|
||||
|
||||
return merge(
|
||||
...Object.keys(runtimeScope.signals).map((key: string) =>
|
||||
fromEventPattern(
|
||||
(handler) => debugValues.view.addSignalListener(key, handler),
|
||||
(handler) => debugValues.view.removeSignalListener(key, handler)
|
||||
)
|
||||
)
|
||||
).pipe(
|
||||
debounce((val) => timer(350)),
|
||||
map(() => debugValues)
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
return merge(this.debugValuesSubject, signalsListener).pipe(
|
||||
filter((debugValues) => Boolean(debugValues)),
|
||||
map((debugValues) => {
|
||||
const runtimeScope = getVegaRuntimeScope(debugValues);
|
||||
|
||||
return {
|
||||
columns: [
|
||||
{ id: vegaAdapterSignalLabel, schema: 'text' },
|
||||
{ id: vegaAdapterValueLabel, schema: 'json' },
|
||||
],
|
||||
data: Object.keys(runtimeScope.signals).map((key: string) =>
|
||||
serializeColumns(
|
||||
{
|
||||
[vegaAdapterSignalLabel]: key,
|
||||
[vegaAdapterValueLabel]: runtimeScope.signals[key].value,
|
||||
},
|
||||
[vegaAdapterSignalLabel, vegaAdapterValueLabel]
|
||||
)
|
||||
),
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
getSpecSubscription(): Observable<string> {
|
||||
return this.debugValuesSubject.pipe(
|
||||
filter((debugValues) => Boolean(debugValues)),
|
||||
map((debugValues) => JSON.stringify(debugValues.spec, null, 2))
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
.vgaVegaDataInspector,
|
||||
.vgaVegaDataInspector__specViewer {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.vgaVegaDataInspector {
|
||||
// TODO: EUI needs to provide props to pass down from EuiTabbedContent to tabs and content
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
[role='tablist'] {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
[role='tabpanel'] {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import './vega_data_inspector.scss';
|
||||
|
||||
import React from 'react';
|
||||
import { EuiTabbedContent } from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { VegaInspectorAdapters } from './vega_inspector';
|
||||
import { DataViewer, SignalViewer, SpecViewer } from './components';
|
||||
import { InspectorViewProps } from '../../../inspector/public';
|
||||
|
||||
export type VegaDataInspectorProps = InspectorViewProps<VegaInspectorAdapters>;
|
||||
|
||||
const dataSetsLabel = i18n.translate('visTypeVega.inspector.dataSetsLabel', {
|
||||
defaultMessage: 'Data sets',
|
||||
});
|
||||
|
||||
const signalValuesLabel = i18n.translate('visTypeVega.inspector.signalValuesLabel', {
|
||||
defaultMessage: 'Signal values',
|
||||
});
|
||||
|
||||
const specLabel = i18n.translate('visTypeVega.inspector.specLabel', {
|
||||
defaultMessage: 'Spec',
|
||||
});
|
||||
|
||||
export const VegaDataInspector = ({ adapters }: VegaDataInspectorProps) => {
|
||||
const tabs = [
|
||||
{
|
||||
id: 'data-viewer--id',
|
||||
name: dataSetsLabel,
|
||||
content: <DataViewer vegaAdapter={adapters.vega} />,
|
||||
},
|
||||
{
|
||||
id: 'signal-viewer--id',
|
||||
name: signalValuesLabel,
|
||||
content: <SignalViewer vegaAdapter={adapters.vega} />,
|
||||
},
|
||||
{
|
||||
id: 'spec-viewer--id',
|
||||
name: specLabel,
|
||||
content: (
|
||||
<SpecViewer className="vgaVegaDataInspector__specViewer" vegaAdapter={adapters.vega} />
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<EuiTabbedContent
|
||||
className="vgaVegaDataInspector"
|
||||
size="s"
|
||||
tabs={tabs}
|
||||
initialSelectedTab={tabs[0]}
|
||||
autoFocus="selected"
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { IUiSettingsClient } from 'kibana/public';
|
||||
import { VegaAdapter } from './vega_adapter';
|
||||
import { VegaDataInspector, VegaDataInspectorProps } from './vega_data_inspector';
|
||||
import { KibanaContextProvider } from '../../../kibana_react/public';
|
||||
import { Adapters, RequestAdapter, InspectorViewDescription } from '../../../inspector/public';
|
||||
|
||||
export interface VegaInspectorAdapters extends Adapters {
|
||||
requests: RequestAdapter;
|
||||
vega: VegaAdapter;
|
||||
}
|
||||
|
||||
const vegaDebugLabel = i18n.translate('visTypeVega.inspector.vegaDebugLabel', {
|
||||
defaultMessage: 'Vega debug',
|
||||
});
|
||||
|
||||
interface VegaInspectorViewDependencies {
|
||||
uiSettings: IUiSettingsClient;
|
||||
}
|
||||
|
||||
export const getVegaInspectorView = (dependencies: VegaInspectorViewDependencies) =>
|
||||
({
|
||||
title: vegaDebugLabel,
|
||||
shouldShow(adapters) {
|
||||
return Boolean(adapters.vega);
|
||||
},
|
||||
component: (props) => (
|
||||
<KibanaContextProvider services={dependencies}>
|
||||
<VegaDataInspector {...(props as VegaDataInspectorProps)}> </VegaDataInspector>
|
||||
</KibanaContextProvider>
|
||||
),
|
||||
} as InspectorViewDescription);
|
||||
|
||||
export const createInspectorAdapters = (): VegaInspectorAdapters => ({
|
||||
requests: new RequestAdapter(),
|
||||
vega: new VegaAdapter(),
|
||||
});
|
|
@ -25,6 +25,7 @@ import { TimeCache } from './data_model/time_cache';
|
|||
import { VegaVisualizationDependencies } from './plugin';
|
||||
import { VisParams } from './vega_fn';
|
||||
import { getData, getInjectedMetadata } from './services';
|
||||
import { VegaInspectorAdapters } from './vega_inspector';
|
||||
|
||||
interface VegaRequestHandlerParams {
|
||||
query: Query;
|
||||
|
@ -33,9 +34,14 @@ interface VegaRequestHandlerParams {
|
|||
visParams: VisParams;
|
||||
}
|
||||
|
||||
interface VegaRequestHandlerContext {
|
||||
abortSignal?: AbortSignal;
|
||||
inspectorAdapters?: VegaInspectorAdapters;
|
||||
}
|
||||
|
||||
export function createVegaRequestHandler(
|
||||
{ plugins: { data }, core: { uiSettings }, serviceSettings }: VegaVisualizationDependencies,
|
||||
abortSignal?: AbortSignal
|
||||
context: VegaRequestHandlerContext = {}
|
||||
) {
|
||||
let searchAPI: SearchAPI;
|
||||
const { timefilter } = data.query.timefilter;
|
||||
|
@ -54,7 +60,8 @@ export function createVegaRequestHandler(
|
|||
search: getData().search,
|
||||
injectedMetadata: getInjectedMetadata(),
|
||||
},
|
||||
abortSignal
|
||||
context.abortSignal,
|
||||
context.inspectorAdapters
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -23,9 +23,10 @@ import { VegaVisualizationDependencies } from './plugin';
|
|||
import { VegaVisEditor } from './components';
|
||||
|
||||
import { createVegaRequestHandler } from './vega_request_handler';
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
import { createVegaVisualization } from './vega_visualization';
|
||||
import { getDefaultSpec } from './default_spec';
|
||||
import { createInspectorAdapters } from './vega_inspector';
|
||||
|
||||
export const createVegaTypeDefinition = (dependencies: VegaVisualizationDependencies) => {
|
||||
const requestHandler = createVegaRequestHandler(dependencies);
|
||||
|
@ -54,5 +55,6 @@ export const createVegaTypeDefinition = (dependencies: VegaVisualizationDependen
|
|||
showFilterBar: true,
|
||||
},
|
||||
stage: 'experimental',
|
||||
inspectorAdapters: createInspectorAdapters,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -364,6 +364,11 @@ export class VegaBaseView {
|
|||
* Set global debug variable to simplify vega debugging in console. Show info message first time
|
||||
*/
|
||||
setDebugValues(view, spec, vlspec) {
|
||||
this._parser.searchAPI.inspectorAdapters?.vega.bindInspectValues({
|
||||
view,
|
||||
spec: vlspec || spec,
|
||||
});
|
||||
|
||||
if (window) {
|
||||
if (window.VEGA_DEBUG === undefined && console) {
|
||||
console.log('%cWelcome to Kibana Vega Plugin!', 'font-size: 16px; font-weight: bold;');
|
||||
|
|
|
@ -142,7 +142,7 @@ export class VegaMapView extends VegaBaseView {
|
|||
});
|
||||
|
||||
const vegaView = vegaMapLayer.getVegaView();
|
||||
this.setDebugValues(vegaView, this._parser.spec, this._parser.vlspec);
|
||||
await this.setView(vegaView);
|
||||
this.setDebugValues(vegaView, this._parser.spec, this._parser.vlspec);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,6 @@ export class VegaView extends VegaBaseView {
|
|||
if (!this._$container) return;
|
||||
|
||||
const view = new vega.View(vega.parse(this._parser.spec), this._vegaViewConfig);
|
||||
this.setDebugValues(view, this._parser.spec, this._parser.vlspec);
|
||||
|
||||
view.warn = this.onWarn.bind(this);
|
||||
view.error = this.onError.bind(this);
|
||||
|
@ -36,5 +35,6 @@ export class VegaView extends VegaBaseView {
|
|||
if (this._parser.useHover) view.hover();
|
||||
|
||||
await this.setView(view);
|
||||
this.setDebugValues(view, this._parser.spec, this._parser.vlspec);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ import {
|
|||
EmbeddableOutput,
|
||||
Embeddable,
|
||||
IContainer,
|
||||
Adapters,
|
||||
} from '../../../../plugins/embeddable/public';
|
||||
import { dispatchRenderComplete } from '../../../../plugins/kibana_utils/public';
|
||||
import {
|
||||
|
@ -78,8 +79,6 @@ export interface VisualizeOutput extends EmbeddableOutput {
|
|||
|
||||
type ExpressionLoader = InstanceType<ExpressionsStart['ExpressionLoader']>;
|
||||
|
||||
const visTypesWithoutInspector = ['markdown', 'input_control_vis', 'metrics', 'vega', 'timelion'];
|
||||
|
||||
export class VisualizeEmbeddable extends Embeddable<VisualizeInput, VisualizeOutput> {
|
||||
private handler?: ExpressionLoader;
|
||||
private timefilter: TimefilterContract;
|
||||
|
@ -96,6 +95,7 @@ export class VisualizeEmbeddable extends Embeddable<VisualizeInput, VisualizeOut
|
|||
private autoRefreshFetchSubscription: Subscription;
|
||||
private abortController?: AbortController;
|
||||
private readonly deps: VisualizeEmbeddableFactoryDeps;
|
||||
private readonly inspectorAdapters?: Adapters;
|
||||
|
||||
constructor(
|
||||
timefilter: TimefilterContract,
|
||||
|
@ -131,13 +131,20 @@ export class VisualizeEmbeddable extends Embeddable<VisualizeInput, VisualizeOut
|
|||
this.handleChanges();
|
||||
})
|
||||
);
|
||||
|
||||
const inspectorAdapters = this.vis.type.inspectorAdapters;
|
||||
|
||||
if (inspectorAdapters) {
|
||||
this.inspectorAdapters =
|
||||
typeof inspectorAdapters === 'function' ? inspectorAdapters() : inspectorAdapters;
|
||||
}
|
||||
}
|
||||
public getVisualizationDescription() {
|
||||
return this.vis.description;
|
||||
}
|
||||
|
||||
public getInspectorAdapters = () => {
|
||||
if (!this.handler || visTypesWithoutInspector.includes(this.vis.type.name)) {
|
||||
if (!this.handler || (this.inspectorAdapters && !Object.keys(this.inspectorAdapters).length)) {
|
||||
return undefined;
|
||||
}
|
||||
return this.handler.inspect();
|
||||
|
@ -349,6 +356,7 @@ export class VisualizeEmbeddable extends Embeddable<VisualizeInput, VisualizeOut
|
|||
filters: this.input.filters,
|
||||
},
|
||||
uiState: this.vis.uiState,
|
||||
inspectorAdapters: this.inspectorAdapters,
|
||||
};
|
||||
if (this.abortController) {
|
||||
this.abortController.abort();
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
import _ from 'lodash';
|
||||
import { VisualizationControllerConstructor } from '../types';
|
||||
import { TriggerContextMapping } from '../../../ui_actions/public';
|
||||
import { Adapters } from '../../../inspector/public';
|
||||
|
||||
export interface BaseVisTypeOptions {
|
||||
name: string;
|
||||
|
@ -40,6 +41,7 @@ export interface BaseVisTypeOptions {
|
|||
hierarchicalData?: boolean | unknown;
|
||||
setup?: unknown;
|
||||
useCustomNoDataScreen?: boolean;
|
||||
inspectorAdapters?: Adapters | (() => Adapters);
|
||||
}
|
||||
|
||||
export class BaseVisType {
|
||||
|
@ -63,6 +65,7 @@ export class BaseVisType {
|
|||
hierarchicalData: boolean | unknown;
|
||||
setup?: unknown;
|
||||
useCustomNoDataScreen: boolean;
|
||||
inspectorAdapters?: Adapters | (() => Adapters);
|
||||
|
||||
constructor(opts: BaseVisTypeOptions) {
|
||||
if (!opts.icon && !opts.image) {
|
||||
|
@ -98,6 +101,7 @@ export class BaseVisType {
|
|||
this.requiresSearch = this.requestHandler !== 'none';
|
||||
this.hierarchicalData = opts.hierarchicalData || false;
|
||||
this.useCustomNoDataScreen = opts.useCustomNoDataScreen || false;
|
||||
this.inspectorAdapters = opts.inspectorAdapters;
|
||||
}
|
||||
|
||||
public get schemas() {
|
||||
|
|
|
@ -22,7 +22,6 @@ import expect from '@kbn/expect';
|
|||
export default function ({ getService, getPageObjects }) {
|
||||
const PageObjects = getPageObjects(['timePicker', 'visualize', 'visChart', 'vegaChart']);
|
||||
const filterBar = getService('filterBar');
|
||||
const inspector = getService('inspector');
|
||||
const log = getService('log');
|
||||
|
||||
describe('vega chart in visualize app', () => {
|
||||
|
@ -35,10 +34,6 @@ export default function ({ getService, getPageObjects }) {
|
|||
|
||||
describe('vega chart', () => {
|
||||
describe('initial render', () => {
|
||||
it('should not have inspector enabled', async function () {
|
||||
await inspector.expectIsNotEnabled();
|
||||
});
|
||||
|
||||
it.skip('should have some initial vega spec text', async function () {
|
||||
const vegaSpec = await PageObjects.vegaChart.getSpec();
|
||||
expect(vegaSpec).to.contain('{').and.to.contain('data');
|
||||
|
|
|
@ -80,7 +80,7 @@ export async function fetchSearchSourceAndRecordWithInspector({
|
|||
inspectorRequest.json(body);
|
||||
});
|
||||
resp = await searchSource.fetch({ abortSignal });
|
||||
inspectorRequest.stats(getResponseInspectorStats(searchSource, resp)).ok({ json: resp });
|
||||
inspectorRequest.stats(getResponseInspectorStats(resp, searchSource)).ok({ json: resp });
|
||||
} catch (error) {
|
||||
inspectorRequest.error({ error });
|
||||
throw error;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue