mirror of
https://github.com/elastic/kibana.git
synced 2025-04-25 02:09:32 -04:00
[Lens] Reduce initial bundle size (#78142)
This commit is contained in:
parent
5cdd93285e
commit
dbef60d3f1
54 changed files with 192 additions and 116 deletions
|
@ -1 +0,0 @@
|
|||
@import 'app';
|
|
@ -4,6 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import './app.scss';
|
||||
|
||||
import _ from 'lodash';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
|
24
x-pack/plugins/lens/public/async_services.ts
Normal file
24
x-pack/plugins/lens/public/async_services.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This file re-exports all parts of visualizations and datasources which can be loaded lazily
|
||||
* (to reduce page load bundle size) when Lens is actually accessed via editor or embeddable.
|
||||
*
|
||||
* It's also possible for each visualization and datasource to resolve this locally, but this causes
|
||||
* a burst of bundles being loaded on Lens startup at once (and in some scenarios cascading bundle loads).
|
||||
* This file causes all of them to be served in a single request.
|
||||
*/
|
||||
|
||||
export * from './datatable_visualization/datatable_visualization';
|
||||
export * from './metric_visualization/metric_visualization';
|
||||
export * from './pie_visualization/pie_visualization';
|
||||
export * from './xy_visualization/xy_visualization';
|
||||
|
||||
export * from './indexpattern_datasource/indexpattern';
|
||||
|
||||
export * from './editor_frame_service/editor_frame';
|
||||
export * from './app_plugin/mounter';
|
|
@ -1 +0,0 @@
|
|||
@import 'visualization';
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export * from './expression';
|
||||
export * from './visualization';
|
|
@ -4,6 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import './expression.scss';
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
|
|
@ -5,9 +5,7 @@
|
|||
*/
|
||||
|
||||
import { CoreSetup } from 'kibana/public';
|
||||
import { datatableVisualization } from './visualization';
|
||||
import { ExpressionsSetup } from '../../../../../src/plugins/expressions/public';
|
||||
import { datatable, datatableColumns, getDatatableRenderer } from './expression';
|
||||
import { EditorFrameSetup, FormatFactory } from '../types';
|
||||
import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public';
|
||||
import { DataPublicPluginStart } from '../../../../../src/plugins/data/public';
|
||||
|
@ -29,6 +27,13 @@ export class DatatableVisualization {
|
|||
core: CoreSetup<DatatableVisualizationPluginStartPlugins, void>,
|
||||
{ expressions, formatFactory, editorFrame }: DatatableVisualizationPluginSetupPlugins
|
||||
) {
|
||||
editorFrame.registerVisualization(async () => {
|
||||
const {
|
||||
datatable,
|
||||
datatableColumns,
|
||||
getDatatableRenderer,
|
||||
datatableVisualization,
|
||||
} = await import('../async_services');
|
||||
expressions.registerFunction(() => datatableColumns);
|
||||
expressions.registerFunction(() => datatable);
|
||||
expressions.registerRenderer(() =>
|
||||
|
@ -39,6 +44,7 @@ export class DatatableVisualization {
|
|||
.then(([_, { data: dataStart }]) => dataStart.search.aggs.types.get),
|
||||
})
|
||||
);
|
||||
editorFrame.registerVisualization(datatableVisualization);
|
||||
return datatableVisualization;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
@import 'drag_drop';
|
|
@ -4,6 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import './drag_drop.scss';
|
||||
|
||||
import React, { useState, useContext } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { DragContext } from './providers';
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
@import 'editor_frame/index';
|
|
@ -1,12 +0,0 @@
|
|||
.lnsExpressionRenderer {
|
||||
@include euiScrollBar;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
overflow: auto;
|
||||
|
||||
.lnsExpressionRenderer__component {
|
||||
position: static; // Let the progress indicator position itself against the outer parent
|
||||
}
|
||||
}
|
|
@ -4,6 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import './data_panel_wrapper.scss';
|
||||
|
||||
import React, { useMemo, memo, useContext, useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiPopover, EuiButtonIcon, EuiContextMenuPanel, EuiContextMenuItem } from '@elastic/eui';
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
@import '../../variables';
|
||||
|
||||
.lnsFrameLayout {
|
||||
padding: 0;
|
||||
position: absolute;
|
|
@ -4,6 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import './frame_layout.scss';
|
||||
|
||||
import React from 'react';
|
||||
import { EuiPage, EuiPageSideBar, EuiPageBody } from '@elastic/eui';
|
||||
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
@import 'data_panel_wrapper';
|
||||
@import 'expression_renderer';
|
||||
@import 'frame_layout';
|
||||
@import 'suggestion_panel';
|
||||
@import 'workspace_panel_wrapper';
|
|
@ -1,3 +1,6 @@
|
|||
@import '../../mixins';
|
||||
@import '../../variables';
|
||||
|
||||
.lnsSuggestionPanel__title {
|
||||
margin-left: $euiSizeXS / 2;
|
||||
}
|
|
@ -4,6 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import './suggestion_panel.scss';
|
||||
|
||||
import _, { camelCase } from 'lodash';
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import './workspace_panel_wrapper.scss';
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import classNames from 'classnames';
|
||||
|
|
|
@ -22,7 +22,6 @@ import {
|
|||
EditorFrameStart,
|
||||
} from '../types';
|
||||
import { Document } from '../persistence/saved_object_store';
|
||||
import { EditorFrame } from './editor_frame';
|
||||
import { mergeTables } from './merge_tables';
|
||||
import { formatColumn } from './format_column';
|
||||
import { EmbeddableFactory, LensEmbeddableStartServices } from './embeddable/embeddable_factory';
|
||||
|
@ -47,9 +46,11 @@ export interface EditorFrameStartPlugins {
|
|||
}
|
||||
|
||||
async function collectAsyncDefinitions<T extends { id: string }>(
|
||||
definitions: Array<T | Promise<T>>
|
||||
definitions: Array<T | (() => Promise<T>)>
|
||||
) {
|
||||
const resolvedDefinitions = await Promise.all(definitions);
|
||||
const resolvedDefinitions = await Promise.all(
|
||||
definitions.map((definition) => (typeof definition === 'function' ? definition() : definition))
|
||||
);
|
||||
const definitionMap: Record<string, T> = {};
|
||||
resolvedDefinitions.forEach((definition) => {
|
||||
definitionMap[definition.id] = definition;
|
||||
|
@ -61,8 +62,8 @@ async function collectAsyncDefinitions<T extends { id: string }>(
|
|||
export class EditorFrameService {
|
||||
constructor() {}
|
||||
|
||||
private readonly datasources: Array<Datasource | Promise<Datasource>> = [];
|
||||
private readonly visualizations: Array<Visualization | Promise<Visualization>> = [];
|
||||
private readonly datasources: Array<Datasource | (() => Promise<Datasource>)> = [];
|
||||
private readonly visualizations: Array<Visualization | (() => Promise<Visualization>)> = [];
|
||||
|
||||
/**
|
||||
* This method takes a Lens saved object as returned from the persistence helper,
|
||||
|
@ -124,7 +125,7 @@ export class EditorFrameService {
|
|||
]);
|
||||
|
||||
return {
|
||||
mount: (
|
||||
mount: async (
|
||||
element,
|
||||
{ doc, onError, dateRange, query, filters, savedQuery, onChange, showNoDataPopover }
|
||||
) => {
|
||||
|
@ -132,6 +133,8 @@ export class EditorFrameService {
|
|||
const firstDatasourceId = Object.keys(resolvedDatasources)[0];
|
||||
const firstVisualizationId = Object.keys(resolvedVisualizations)[0];
|
||||
|
||||
const { EditorFrame } = await import('../async_services');
|
||||
|
||||
render(
|
||||
<I18nProvider>
|
||||
<EditorFrame
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
// Import the EUI global scope so we can use EUI constants
|
||||
@import '@elastic/eui/src/global_styling/variables/index';
|
||||
@import '@elastic/eui/src/global_styling/mixins/index';
|
||||
|
||||
@import 'variables';
|
||||
@import 'mixins';
|
||||
|
||||
@import 'app_plugin/index';
|
||||
@import 'datatable_visualization/index';
|
||||
@import 'drag_drop/index';
|
||||
@import 'editor_frame_service/index';
|
||||
@import 'indexpattern_datasource/index';
|
||||
@import 'xy_visualization/index';
|
||||
@import 'metric_visualization/index';
|
|
@ -1 +0,0 @@
|
|||
@import 'field_item';
|
|
@ -4,6 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import './field_item.scss';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import DateMath from '@elastic/datemath';
|
||||
import {
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
|
||||
import { CoreSetup } from 'kibana/public';
|
||||
import { Storage } from '../../../../../src/plugins/kibana_utils/public';
|
||||
import { getIndexPatternDatasource } from './indexpattern';
|
||||
import { renameColumns } from './rename_columns';
|
||||
import { ExpressionsSetup } from '../../../../../src/plugins/expressions/public';
|
||||
import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public';
|
||||
import {
|
||||
|
@ -34,17 +32,17 @@ export class IndexPatternDatasource {
|
|||
core: CoreSetup<IndexPatternDatasourceStartPlugins>,
|
||||
{ expressions, editorFrame, charts }: IndexPatternDatasourceSetupPlugins
|
||||
) {
|
||||
editorFrame.registerDatasource(async () => {
|
||||
const { getIndexPatternDatasource, renameColumns } = await import('../async_services');
|
||||
expressions.registerFunction(renameColumns);
|
||||
|
||||
editorFrame.registerDatasource(
|
||||
core.getStartServices().then(([coreStart, { data }]) =>
|
||||
return core.getStartServices().then(([coreStart, { data }]) =>
|
||||
getIndexPatternDatasource({
|
||||
core: coreStart,
|
||||
storage: new Storage(localStorage),
|
||||
data,
|
||||
charts,
|
||||
})
|
||||
) as Promise<Datasource>
|
||||
);
|
||||
) as Promise<Datasource>;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -104,6 +104,8 @@ export function uniqueLabels(layers: Record<string, IndexPatternLayer>) {
|
|||
return columnLabelMap;
|
||||
}
|
||||
|
||||
export * from './rename_columns';
|
||||
|
||||
export function getIndexPatternDatasource({
|
||||
core,
|
||||
storage,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { metricChart, MetricChart } from './metric_expression';
|
||||
import { metricChart, MetricChart } from './expression';
|
||||
import { LensMultiTable } from '../types';
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
|
@ -4,6 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import './expression.scss';
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import {
|
|
@ -5,9 +5,7 @@
|
|||
*/
|
||||
|
||||
import { CoreSetup } from 'kibana/public';
|
||||
import { metricVisualization } from './metric_visualization';
|
||||
import { ExpressionsSetup } from '../../../../../src/plugins/expressions/public';
|
||||
import { metricChart, getMetricChartRenderer } from './metric_expression';
|
||||
import { EditorFrameSetup, FormatFactory } from '../types';
|
||||
|
||||
export interface MetricVisualizationPluginSetupPlugins {
|
||||
|
@ -23,10 +21,15 @@ export class MetricVisualization {
|
|||
_core: CoreSetup | null,
|
||||
{ expressions, formatFactory, editorFrame }: MetricVisualizationPluginSetupPlugins
|
||||
) {
|
||||
editorFrame.registerVisualization(async () => {
|
||||
const { metricVisualization, metricChart, getMetricChartRenderer } = await import(
|
||||
'../async_services'
|
||||
);
|
||||
|
||||
expressions.registerFunction(() => metricChart);
|
||||
|
||||
expressions.registerRenderer(() => getMetricChartRenderer(formatFactory));
|
||||
|
||||
editorFrame.registerVisualization(metricVisualization);
|
||||
return metricVisualization;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export * from './expression';
|
||||
export * from './visualization';
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { metricVisualization } from './metric_visualization';
|
||||
import { metricVisualization } from './visualization';
|
||||
import { State } from './types';
|
||||
import { createMockDatasource, createMockFramePublicAPI } from '../editor_frame_service/mocks';
|
||||
import { generateId } from '../id_generator';
|
|
@ -6,8 +6,6 @@
|
|||
|
||||
import { CoreSetup } from 'src/core/public';
|
||||
import { ExpressionsSetup } from 'src/plugins/expressions/public';
|
||||
import { pieVisualization } from './pie_visualization';
|
||||
import { pie, getPieRenderer } from './register_expression';
|
||||
import { EditorFrameSetup, FormatFactory } from '../types';
|
||||
import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public';
|
||||
import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public';
|
||||
|
@ -30,6 +28,9 @@ export class PieVisualization {
|
|||
core: CoreSetup,
|
||||
{ expressions, formatFactory, editorFrame, charts }: PieVisualizationPluginSetupPlugins
|
||||
) {
|
||||
editorFrame.registerVisualization(async () => {
|
||||
const { pieVisualization, pie, getPieRenderer } = await import('../async_services');
|
||||
|
||||
expressions.registerFunction(() => pie);
|
||||
|
||||
expressions.registerRenderer(
|
||||
|
@ -38,7 +39,7 @@ export class PieVisualization {
|
|||
chartsThemeService: charts.theme,
|
||||
})
|
||||
);
|
||||
|
||||
editorFrame.registerVisualization(pieVisualization);
|
||||
return pieVisualization;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export * from './expression';
|
||||
export * from './visualization';
|
|
@ -35,7 +35,6 @@ import { EditorFrameStart } from './types';
|
|||
import { getLensAliasConfig } from './vis_type_alias';
|
||||
import { getSearchProvider } from './search_provider';
|
||||
|
||||
import './index.scss';
|
||||
import { getLensAttributeService, LensAttributeService } from './lens_attribute_service';
|
||||
|
||||
export interface LensPluginSetupDependencies {
|
||||
|
@ -127,7 +126,7 @@ export class LensPlugin {
|
|||
title: NOT_INTERNATIONALIZED_PRODUCT_NAME,
|
||||
navLinkStatus: AppNavLinkStatus.hidden,
|
||||
mount: async (params: AppMountParameters) => {
|
||||
const { mountApp } = await import('./app_plugin/mounter');
|
||||
const { mountApp } = await import('./async_services');
|
||||
return mountApp(core, params, {
|
||||
createEditorFrame: this.createEditorFrame!,
|
||||
attributeService: this.attributeService!,
|
||||
|
|
|
@ -57,8 +57,12 @@ export interface EditorFrameInstance {
|
|||
|
||||
export interface EditorFrameSetup {
|
||||
// generic type on the API functions to pull the "unknown vs. specific type" error into the implementation
|
||||
registerDatasource: <T, P>(datasource: Datasource<T, P> | Promise<Datasource<T, P>>) => void;
|
||||
registerVisualization: <T>(visualization: Visualization<T> | Promise<Visualization<T>>) => void;
|
||||
registerDatasource: <T, P>(
|
||||
datasource: Datasource<T, P> | (() => Promise<Datasource<T, P>>)
|
||||
) => void;
|
||||
registerVisualization: <T>(
|
||||
visualization: Visualization<T> | (() => Promise<Visualization<T>>)
|
||||
) => void;
|
||||
}
|
||||
|
||||
export interface EditorFrameStart {
|
||||
|
|
|
@ -2,3 +2,16 @@
|
|||
@include euiScrollBar;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.lnsExpressionRenderer {
|
||||
@include euiScrollBar;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
overflow: auto;
|
||||
|
||||
.lnsExpressionRenderer__component {
|
||||
position: static; // Let the progress indicator position itself against the outer parent
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import './visualization_container.scss';
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import './visualization_container.scss';
|
||||
|
||||
interface Props extends React.HTMLAttributes<HTMLDivElement> {
|
||||
isReady?: boolean;
|
||||
reportTitle?: string;
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
@import 'xy_expression';
|
|
@ -17,7 +17,7 @@ import {
|
|||
SeriesNameFn,
|
||||
Fit,
|
||||
} from '@elastic/charts';
|
||||
import { xyChart, XYChart } from './xy_expression';
|
||||
import { xyChart, XYChart } from './expression';
|
||||
import { LensMultiTable } from '../types';
|
||||
import { KibanaDatatable, KibanaDatatableRow } from '../../../../../src/plugins/expressions/public';
|
||||
import React from 'react';
|
|
@ -4,6 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import './expression.scss';
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import moment from 'moment';
|
|
@ -8,16 +8,6 @@ import { CoreSetup, IUiSettingsClient } from 'kibana/public';
|
|||
import moment from 'moment-timezone';
|
||||
import { ExpressionsSetup } from '../../../../../src/plugins/expressions/public';
|
||||
import { UI_SETTINGS } from '../../../../../src/plugins/data/public';
|
||||
import { xyVisualization } from './xy_visualization';
|
||||
import { xyChart, getXyChartRenderer } from './xy_expression';
|
||||
import {
|
||||
legendConfig,
|
||||
layerConfig,
|
||||
yAxisConfig,
|
||||
tickLabelsConfig,
|
||||
gridlinesConfig,
|
||||
axisTitlesVisibilityConfig,
|
||||
} from './types';
|
||||
import { EditorFrameSetup, FormatFactory } from '../types';
|
||||
import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public';
|
||||
|
||||
|
@ -44,6 +34,18 @@ export class XyVisualization {
|
|||
core: CoreSetup,
|
||||
{ expressions, formatFactory, editorFrame, charts }: XyVisualizationPluginSetupPlugins
|
||||
) {
|
||||
editorFrame.registerVisualization(async () => {
|
||||
const {
|
||||
legendConfig,
|
||||
yAxisConfig,
|
||||
tickLabelsConfig,
|
||||
gridlinesConfig,
|
||||
axisTitlesVisibilityConfig,
|
||||
layerConfig,
|
||||
xyChart,
|
||||
getXyChartRenderer,
|
||||
xyVisualization,
|
||||
} = await import('../async_services');
|
||||
expressions.registerFunction(() => legendConfig);
|
||||
expressions.registerFunction(() => yAxisConfig);
|
||||
expressions.registerFunction(() => tickLabelsConfig);
|
||||
|
@ -60,7 +62,7 @@ export class XyVisualization {
|
|||
histogramBarTarget: core.uiSettings.get<number>(UI_SETTINGS.HISTOGRAM_BAR_TARGET),
|
||||
})
|
||||
);
|
||||
|
||||
editorFrame.registerVisualization(xyVisualization);
|
||||
return xyVisualization;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { xyVisualization } from './xy_visualization';
|
||||
import { xyVisualization } from './visualization';
|
||||
import { Position } from '@elastic/charts';
|
||||
import { Operation } from '../types';
|
||||
import { State, SeriesType } from './types';
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export * from './expression';
|
||||
export * from './types';
|
||||
export * from './visualization';
|
Loading…
Add table
Add a link
Reference in a new issue