diff --git a/src/plugins/vis_type_vislib/public/index.ts b/src/plugins/vis_type_vislib/public/index.ts
index 525be8eaf829..1b13f06ca6d2 100644
--- a/src/plugins/vis_type_vislib/public/index.ts
+++ b/src/plugins/vis_type_vislib/public/index.ts
@@ -35,4 +35,6 @@ export function plugin(initializerContext: PluginInitializerContext) {
return new Plugin(initializerContext);
}
+export { getConfigCollections } from './utils/collections';
+
export * from './types';
diff --git a/src/plugins/vis_type_vislib/public/types.ts b/src/plugins/vis_type_vislib/public/types.ts
index 3416a1ee3c4a..3267f04db87b 100644
--- a/src/plugins/vis_type_vislib/public/types.ts
+++ b/src/plugins/vis_type_vislib/public/types.ts
@@ -106,3 +106,5 @@ export interface BasicVislibParams extends CommonVislibParams {
times: TimeMarker[];
type: string;
}
+
+export { Positions };
diff --git a/src/plugins/visualizations/public/index.ts b/src/plugins/visualizations/public/index.ts
index 1b91816b3fc5..a522395af1ba 100644
--- a/src/plugins/visualizations/public/index.ts
+++ b/src/plugins/visualizations/public/index.ts
@@ -43,7 +43,7 @@ export { Vis } from './vis';
export { TypesService } from './vis_types/types_service';
export { VISUALIZE_EMBEDDABLE_TYPE, VIS_EVENT_TO_TRIGGER } from './embeddable';
export { VisualizationContainer, VisualizationNoResults } from './components';
-export { getSchemas as getVisSchemas } from './legacy/build_pipeline';
+export { getSchemas as getVisSchemas, buildVislibDimensions } from './legacy/build_pipeline';
/** @public types */
export { VisualizationsSetup, VisualizationsStart };
diff --git a/src/plugins/wizard/opensearch_dashboards.json b/src/plugins/wizard/opensearch_dashboards.json
index 020ca5f2ab9b..ae088bfe4c8f 100644
--- a/src/plugins/wizard/opensearch_dashboards.json
+++ b/src/plugins/wizard/opensearch_dashboards.json
@@ -16,7 +16,8 @@
"dashboard",
"visualizations",
"opensearchUiShared",
- "visDefaultEditor"
+ "visDefaultEditor",
+ "visTypeVislib"
],
"optionalPlugins": []
}
diff --git a/src/plugins/wizard/public/application/_variables.scss b/src/plugins/wizard/public/application/_variables.scss
index 2baa9db275f2..da530e2e46a4 100644
--- a/src/plugins/wizard/public/application/_variables.scss
+++ b/src/plugins/wizard/public/application/_variables.scss
@@ -6,4 +6,4 @@
@import "@elastic/eui/src/global_styling/variables/form";
$osdHeaderOffset: $euiHeaderHeightCompensation;
-$wizSideNavWidth: 470px;
+$wizLeftNavWidth: 462px;
diff --git a/src/plugins/wizard/public/application/app.scss b/src/plugins/wizard/public/application/app.scss
index f38959445100..f4a73eb3ecd4 100644
--- a/src/plugins/wizard/public/application/app.scss
+++ b/src/plugins/wizard/public/application/app.scss
@@ -8,9 +8,18 @@
padding: 0;
display: grid;
grid-template:
- "topNav topNav topNav" min-content
- "leftNav workspace rightNav" 1fr / #{$wizSideNavWidth} 1fr #{$wizSideNavWidth};
+ "topNav topNav" min-content
+ "leftNav workspaceNav" 1fr / #{$wizLeftNavWidth} 1fr;
height: calc(100vh - #{$osdHeaderOffset});
+
+ &__resizeContainer {
+ min-height: 0;
+ background-color: $euiColorEmptyShade;
+ }
+
+ &__resizeButton {
+ transform: translateX(-$euiSizeM / 2);
+ }
}
.headerIsExpanded .wizLayout {
diff --git a/src/plugins/wizard/public/application/app.tsx b/src/plugins/wizard/public/application/app.tsx
index 9c93dcb1b552..52a0f537646e 100644
--- a/src/plugins/wizard/public/application/app.tsx
+++ b/src/plugins/wizard/public/application/app.tsx
@@ -5,7 +5,7 @@
import React from 'react';
import { I18nProvider } from '@osd/i18n/react';
-import { EuiPage } from '@elastic/eui';
+import { EuiPage, EuiResizableContainer } from '@elastic/eui';
import { DragDropProvider } from './utils/drag_drop/drag_drop_context';
import { LeftNav } from './components/left_nav';
import { TopNav } from './components/top_nav';
@@ -21,8 +21,37 @@ export const WizardApp = () => {
-
-
+
+ {(EuiResizablePanel, EuiResizableButton) => (
+ <>
+
+
+
+
+
+
+
+ >
+ )}
+
diff --git a/src/plugins/wizard/public/application/components/data_tab/field_selector.scss b/src/plugins/wizard/public/application/components/data_tab/field_selector.scss
index b2fb337e1dc2..b9b75a8a260d 100644
--- a/src/plugins/wizard/public/application/components/data_tab/field_selector.scss
+++ b/src/plugins/wizard/public/application/components/data_tab/field_selector.scss
@@ -15,6 +15,8 @@
overflow-y: auto;
margin-right: -$euiSizeS;
padding-right: $euiSizeS;
+ margin-left: -$euiSizeS;
+ padding-left: $euiSizeS;
margin-top: $euiSizeS;
}
diff --git a/src/plugins/wizard/public/application/components/experimental_info.tsx b/src/plugins/wizard/public/application/components/experimental_info.tsx
index cc4d0cab1340..35ea235c1ba3 100644
--- a/src/plugins/wizard/public/application/components/experimental_info.tsx
+++ b/src/plugins/wizard/public/application/components/experimental_info.tsx
@@ -6,37 +6,35 @@
import React, { memo } from 'react';
import { EuiCallOut, EuiLink } from '@elastic/eui';
import { FormattedMessage } from '@osd/i18n/react';
+import { i18n } from '@osd/i18n';
export const InfoComponent = () => {
- const title = (
- <>
+ return (
+
- GitHub
+ the GitHub issue
),
}}
/>
- >
- );
-
- return (
-
+
);
};
diff --git a/src/plugins/wizard/public/application/components/right_nav.tsx b/src/plugins/wizard/public/application/components/right_nav.tsx
index bbd1ce844c2c..ea7c3e17eab6 100644
--- a/src/plugins/wizard/public/application/components/right_nav.tsx
+++ b/src/plugins/wizard/public/application/components/right_nav.tsx
@@ -3,8 +3,16 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import React from 'react';
-import { EuiSuperSelect, EuiSuperSelectOption, EuiIcon, IconType } from '@elastic/eui';
+import React, { useState } from 'react';
+import {
+ EuiSuperSelect,
+ EuiSuperSelectOption,
+ EuiIcon,
+ IconType,
+ EuiConfirmModal,
+} from '@elastic/eui';
+import { i18n } from '@osd/i18n';
+import { FormattedMessage } from '@osd/i18n/react';
import { useVisualizationType } from '../utils/use';
import './side_nav.scss';
import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public';
@@ -12,6 +20,7 @@ import { WizardServices } from '../../types';
import { setActiveVisualization, useTypedDispatch } from '../utils/state_management';
export const RightNav = () => {
+ const [newVisType, setNewVisType] = useState();
const {
services: { types },
} = useOpenSearchDashboards();
@@ -23,6 +32,7 @@ export const RightNav = () => {
value: name,
inputDisplay: ,
dropdownDisplay: ,
+ 'data-test-subj': `visType-${name}`,
}));
return (
@@ -32,19 +42,48 @@ export const RightNav = () => {
options={options}
valueOfSelected={activeVisName}
onChange={(name) => {
- dispatch(
- setActiveVisualization({
- name,
- style: types.get(name)?.ui.containerConfig.style.defaults,
- })
- );
+ setNewVisType(name);
}}
fullWidth
+ data-test-subj="chartPicker"
/>
+ {newVisType && (
+ setNewVisType(undefined)}
+ onConfirm={() => {
+ dispatch(
+ setActiveVisualization({
+ name: newVisType,
+ style: types.get(newVisType)?.ui.containerConfig.style.defaults,
+ })
+ );
+
+ setNewVisType(undefined);
+ }}
+ maxWidth="300px"
+ data-test-subj="confirmVisChangeModal"
+ >
+
+
+
+
+ )}
);
};
diff --git a/src/plugins/wizard/public/application/components/searchable_dropdown.scss b/src/plugins/wizard/public/application/components/searchable_dropdown.scss
index de03454dffbe..be8d6144df3a 100644
--- a/src/plugins/wizard/public/application/components/searchable_dropdown.scss
+++ b/src/plugins/wizard/public/application/components/searchable_dropdown.scss
@@ -22,7 +22,7 @@
}
&--fixedWidthChild {
- width: calc(#{$wizSideNavWidth} - #{$euiSizeXL} * 2);
+ width: calc(#{$wizLeftNavWidth} - #{$euiSizeXL} * 2);
}
&--selectableWrapper .euiSelectableList {
diff --git a/src/plugins/wizard/public/application/components/side_nav.scss b/src/plugins/wizard/public/application/components/side_nav.scss
index 5fade4b11b4d..025e42d08170 100644
--- a/src/plugins/wizard/public/application/components/side_nav.scss
+++ b/src/plugins/wizard/public/application/components/side_nav.scss
@@ -16,6 +16,7 @@
&.right {
border-left: $euiBorderThin;
grid-area: rightNav;
+ height: 100%;
}
&__header {
@@ -46,5 +47,5 @@
}
.wizDatasourceSelect {
- max-width: calc(#{$wizSideNavWidth} - 1px);
+ max-width: calc(#{$wizLeftNavWidth} - 1px);
}
diff --git a/src/plugins/wizard/public/application/components/workspace.scss b/src/plugins/wizard/public/application/components/workspace.scss
index 0b7b851cfddb..1a57a891262e 100644
--- a/src/plugins/wizard/public/application/components/workspace.scss
+++ b/src/plugins/wizard/public/application/components/workspace.scss
@@ -10,6 +10,7 @@
grid-gap: $euiSizeM;
padding: $euiSizeM;
background-color: $euiColorEmptyShade;
+ height: 100%;
&__empty {
height: 100%;
diff --git a/src/plugins/wizard/public/application/components/workspace.tsx b/src/plugins/wizard/public/application/components/workspace.tsx
index 7e51a29be1a6..1aa58a272dff 100644
--- a/src/plugins/wizard/public/application/components/workspace.tsx
+++ b/src/plugins/wizard/public/application/components/workspace.tsx
@@ -11,6 +11,7 @@ import { WizardServices } from '../../types';
import { validateSchemaState } from '../utils/validate_schema_state';
import { useTypedSelector } from '../utils/state_management';
import { useVisualizationType } from '../utils/use';
+import { PersistedState } from '../../../../visualizations/public';
import hand_field from '../../assets/hand_field.svg';
import fields_bg from '../../assets/fields_bg.svg';
@@ -34,6 +35,8 @@ export const Workspace: FC = ({ children }) => {
timeRange: data.query.timefilter.timefilter.getTime(),
});
const rootState = useTypedSelector((state) => state);
+ // Visualizations require the uiState to persist even when the expression changes
+ const uiState = useMemo(() => new PersistedState(), []);
useEffect(() => {
async function loadExpression() {
@@ -77,7 +80,11 @@ export const Workspace: FC = ({ children }) => {
{expression ? (
-
+
) : (
{
+ const { activeVisualization, indexPattern: indexId = '' } = visualization;
+ const { aggConfigParams } = activeVisualization || {};
+
+ const indexPatternsService = getIndexPatterns();
+ const indexPattern = await indexPatternsService.get(indexId);
+ // aggConfigParams is the serealizeable aggConfigs that need to be reconstructed here using the agg servce
+ const aggConfigs = getAggService().createAggConfigs(indexPattern, cloneDeep(aggConfigParams));
+
+ const opensearchDashboards = buildExpressionFunction(
+ 'opensearchDashboards',
+ {}
+ );
+
+ // soon this becomes: const opensearchaggs = vis.data.aggs!.toExpressionAst();
+ const opensearchaggs = buildExpressionFunction(
+ 'opensearchaggs',
+ {
+ index: indexId,
+ metricsAtAllLevels: false,
+ partialRows: false,
+ aggConfigs: JSON.stringify(aggConfigs.aggs),
+ includeFormatHints: false,
+ }
+ );
+
+ return {
+ aggConfigs,
+ expressionFns: [opensearchDashboards, opensearchaggs],
+ };
+};
diff --git a/src/plugins/wizard/public/visualizations/index.ts b/src/plugins/wizard/public/visualizations/index.ts
index 52d3a7234f2a..6787c28a6ff8 100644
--- a/src/plugins/wizard/public/visualizations/index.ts
+++ b/src/plugins/wizard/public/visualizations/index.ts
@@ -5,9 +5,15 @@
import type { TypeServiceSetup } from '../services/type_service';
import { createMetricConfig } from './metric';
+import { createHistogramConfig, createLineConfig, createAreaConfig } from './vislib';
export function registerDefaultTypes(typeServiceSetup: TypeServiceSetup) {
- const visualizationTypes = [createMetricConfig];
+ const visualizationTypes = [
+ createHistogramConfig,
+ createLineConfig,
+ createAreaConfig,
+ createMetricConfig,
+ ];
visualizationTypes.forEach((createTypeConfig) => {
typeServiceSetup.createVisualizationType(createTypeConfig());
diff --git a/src/plugins/wizard/public/visualizations/metric/to_expression.ts b/src/plugins/wizard/public/visualizations/metric/to_expression.ts
index ce930d9b8e40..181e504ec0e8 100644
--- a/src/plugins/wizard/public/visualizations/metric/to_expression.ts
+++ b/src/plugins/wizard/public/visualizations/metric/to_expression.ts
@@ -3,18 +3,13 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import { cloneDeep } from 'lodash';
import { SchemaConfig } from '../../../../visualizations/public';
import { MetricVisExpressionFunctionDefinition } from '../../../../vis_type_metric/public';
-import {
- AggConfigs,
- IAggConfig,
- OpenSearchaggsExpressionFunctionDefinition,
-} from '../../../../data/common';
+import { AggConfigs, IAggConfig } from '../../../../data/common';
import { buildExpression, buildExpressionFunction } from '../../../../expressions/public';
import { RootState } from '../../application/utils/state_management';
import { MetricOptionsDefaults } from './metric_viz_type';
-import { getAggService, getIndexPatterns } from '../../plugin_services';
+import { getAggExpressionFunctions } from '../common/expression_helpers';
const prepareDimension = (params: SchemaConfig) => {
const visdimension = buildExpressionFunction('visdimension', { accessor: params.accessor });
@@ -92,24 +87,7 @@ export interface MetricRootState extends RootState {
}
export const toExpression = async ({ style: styleState, visualization }: MetricRootState) => {
- const { activeVisualization, indexPattern: indexId = '' } = visualization;
- const { aggConfigParams } = activeVisualization || {};
-
- const indexPatternsService = getIndexPatterns();
- const indexPattern = await indexPatternsService.get(indexId);
- const aggConfigs = getAggService().createAggConfigs(indexPattern, cloneDeep(aggConfigParams));
-
- // soon this becomes: const opensearchaggs = vis.data.aggs!.toExpressionAst();
- const opensearchaggs = buildExpressionFunction(
- 'opensearchaggs',
- {
- index: indexId,
- metricsAtAllLevels: false,
- partialRows: false,
- aggConfigs: JSON.stringify(aggConfigs.aggs),
- includeFormatHints: false,
- }
- );
+ const { aggConfigs, expressionFns } = await getAggExpressionFunctions(visualization);
// TODO: Update to use the getVisSchemas function from the Visualizations plugin
// const schemas = getVisSchemas(vis, params);
@@ -169,7 +147,5 @@ export const toExpression = async ({ style: styleState, visualization }: MetricR
metricVis.addArgument('metric', prepareDimension(metric));
});
- const ast = buildExpression([opensearchaggs, metricVis]);
-
- return `opensearchDashboards | opensearch_dashboards_context | ${ast.toString()}`;
+ return buildExpression([...expressionFns, metricVis]).toString();
};
diff --git a/src/plugins/wizard/public/visualizations/vislib/area/area_vis_type.ts b/src/plugins/wizard/public/visualizations/vislib/area/area_vis_type.ts
new file mode 100644
index 000000000000..14b524bf008f
--- /dev/null
+++ b/src/plugins/wizard/public/visualizations/vislib/area/area_vis_type.ts
@@ -0,0 +1,64 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { i18n } from '@osd/i18n';
+import { Schemas } from '../../../../../vis_default_editor/public';
+import { Positions } from '../../../../../vis_type_vislib/public';
+import { AggGroupNames } from '../../../../../data/public';
+import { AreaVisOptions } from './components/area_vis_options';
+import { VisualizationTypeOptions } from '../../../services/type_service';
+import { toExpression } from './to_expression';
+import { BasicOptionsDefaults } from '../common/types';
+
+export interface AreaOptionsDefaults extends BasicOptionsDefaults {
+ type: 'area';
+}
+
+export const createAreaConfig = (): VisualizationTypeOptions => ({
+ name: 'area',
+ title: 'Area',
+ icon: 'visArea',
+ description: 'Display area chart',
+ toExpression,
+ ui: {
+ containerConfig: {
+ data: {
+ schemas: new Schemas([
+ {
+ group: AggGroupNames.Metrics,
+ name: 'metric',
+ title: i18n.translate('visTypeVislib.area.metricTitle', {
+ defaultMessage: 'Y-axis',
+ }),
+ min: 1,
+ max: 3,
+ aggFilter: ['!geo_centroid', '!geo_bounds'],
+ defaults: { aggTypes: ['median'] },
+ },
+ {
+ group: AggGroupNames.Buckets,
+ name: 'segment',
+ title: i18n.translate('visTypeVislib.area.segmentTitle', {
+ defaultMessage: 'X-axis',
+ }),
+ min: 0,
+ max: 1,
+ aggFilter: ['!geohash_grid', '!geotile_grid', '!filter', '!filters'],
+ defaults: { aggTypes: ['terms'] },
+ },
+ ]),
+ },
+ style: {
+ defaults: {
+ addTooltip: true,
+ addLegend: true,
+ legendPosition: Positions.RIGHT,
+ type: 'area',
+ },
+ render: AreaVisOptions,
+ },
+ },
+ },
+});
diff --git a/src/plugins/wizard/public/visualizations/vislib/area/components/area_vis_options.tsx b/src/plugins/wizard/public/visualizations/vislib/area/components/area_vis_options.tsx
new file mode 100644
index 000000000000..4b3116c83992
--- /dev/null
+++ b/src/plugins/wizard/public/visualizations/vislib/area/components/area_vis_options.tsx
@@ -0,0 +1,41 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React, { useCallback } from 'react';
+import { i18n } from '@osd/i18n';
+import produce, { Draft } from 'immer';
+import { useTypedDispatch, useTypedSelector } from '../../../../application/utils/state_management';
+import { AreaOptionsDefaults } from '../area_vis_type';
+import { setState } from '../../../../application/utils/state_management/style_slice';
+import { Option } from '../../../../application/app';
+import { BasicVisOptions } from '../../common/basic_vis_options';
+
+function AreaVisOptions() {
+ const styleState = useTypedSelector((state) => state.style) as AreaOptionsDefaults;
+ const dispatch = useTypedDispatch();
+
+ const setOption = useCallback(
+ (callback: (draft: Draft) => void) => {
+ const newState = produce(styleState, callback);
+ dispatch(setState(newState));
+ },
+ [dispatch, styleState]
+ );
+
+ return (
+ <>
+
+ >
+ );
+}
+
+export { AreaVisOptions };
diff --git a/src/plugins/wizard/public/visualizations/vislib/area/index.ts b/src/plugins/wizard/public/visualizations/vislib/area/index.ts
new file mode 100644
index 000000000000..7ec1f37a601d
--- /dev/null
+++ b/src/plugins/wizard/public/visualizations/vislib/area/index.ts
@@ -0,0 +1,6 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+export { createAreaConfig } from './area_vis_type';
diff --git a/src/plugins/wizard/public/visualizations/vislib/area/to_expression.ts b/src/plugins/wizard/public/visualizations/vislib/area/to_expression.ts
new file mode 100644
index 000000000000..012c6589f57b
--- /dev/null
+++ b/src/plugins/wizard/public/visualizations/vislib/area/to_expression.ts
@@ -0,0 +1,46 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { Vis, buildVislibDimensions } from '../../../../../visualizations/public';
+import { buildExpression, buildExpressionFunction } from '../../../../../expressions/public';
+import { AreaOptionsDefaults } from './area_vis_type';
+import { getAggExpressionFunctions } from '../../common/expression_helpers';
+import { VislibRootState } from '../common/types';
+import { getValueAxes } from '../common/get_value_axes';
+
+export const toExpression = async ({
+ style: styleState,
+ visualization,
+}: VislibRootState) => {
+ const { aggConfigs, expressionFns } = await getAggExpressionFunctions(visualization);
+ const { addLegend, addTooltip, legendPosition, type } = styleState;
+ const pipelineConfigs = {
+ // todo: this will blow up for time x dimensions
+ timefilter: null, // todo: get the time filter from elsewhere
+ };
+
+ const vis = new Vis(type);
+ vis.data.aggs = aggConfigs;
+
+ const dimensions = await buildVislibDimensions(vis, pipelineConfigs as any);
+ const valueAxes = getValueAxes(dimensions.y);
+
+ // TODO: what do we want to put in this "vis config"?
+ const visConfig = {
+ addLegend,
+ legendPosition,
+ addTimeMarker: false,
+ addTooltip,
+ dimensions,
+ valueAxes,
+ };
+
+ const vislib = buildExpressionFunction('vislib', {
+ type,
+ visConfig: JSON.stringify(visConfig),
+ });
+
+ return buildExpression([...expressionFns, vislib]).toString();
+};
diff --git a/src/plugins/wizard/public/visualizations/vislib/common/basic_vis_options.tsx b/src/plugins/wizard/public/visualizations/vislib/common/basic_vis_options.tsx
new file mode 100644
index 000000000000..6b088a68547f
--- /dev/null
+++ b/src/plugins/wizard/public/visualizations/vislib/common/basic_vis_options.tsx
@@ -0,0 +1,49 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { i18n } from '@osd/i18n';
+import React from 'react';
+import { Draft } from 'immer';
+import { SelectOption, SwitchOption } from '../../../../../charts/public';
+import { getConfigCollections } from '../../../../../vis_type_vislib/public';
+import { BasicOptionsDefaults } from './types';
+
+interface Props {
+ styleState: BasicOptionsDefaults;
+ setOption: (callback: (draft: Draft) => void) => void;
+}
+
+export const BasicVisOptions = ({ styleState, setOption }: Props) => {
+ const { legendPositions } = getConfigCollections();
+ return (
+ <>
+
+ setOption((draft) => {
+ draft.legendPosition = value;
+ })
+ }
+ />
+
+ setOption((draft) => {
+ draft.addTooltip = value;
+ })
+ }
+ />
+ >
+ );
+};
diff --git a/src/plugins/wizard/public/visualizations/vislib/common/get_value_axes.ts b/src/plugins/wizard/public/visualizations/vislib/common/get_value_axes.ts
new file mode 100644
index 000000000000..86c135110f50
--- /dev/null
+++ b/src/plugins/wizard/public/visualizations/vislib/common/get_value_axes.ts
@@ -0,0 +1,31 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { SchemaConfig } from '../../../../../visualizations/public';
+import { ValueAxis } from '../../../../../vis_type_vislib/public';
+
+interface ValueAxisConfig extends ValueAxis {
+ style: any;
+}
+
+export const getValueAxes = (yAxes: SchemaConfig[]): ValueAxisConfig[] =>
+ yAxes.map((y, index) => ({
+ id: `ValueAxis-${index + 1}`,
+ labels: {
+ show: true,
+ },
+ name: `ValueAxis-${index + 1}`,
+ position: 'left',
+ scale: {
+ type: 'linear',
+ mode: 'normal',
+ },
+ show: true,
+ style: {},
+ title: {
+ text: y.label,
+ },
+ type: 'value',
+ }));
diff --git a/src/plugins/wizard/public/visualizations/vislib/common/types.ts b/src/plugins/wizard/public/visualizations/vislib/common/types.ts
new file mode 100644
index 000000000000..4861c1af2e55
--- /dev/null
+++ b/src/plugins/wizard/public/visualizations/vislib/common/types.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { Positions } from '../../../../../vis_type_vislib/public';
+import { RootState } from '../../../application/utils/state_management';
+
+export interface BasicOptionsDefaults {
+ addTooltip: boolean;
+ addLegend: boolean;
+ legendPosition: Positions;
+ type: string;
+}
+
+export interface VislibRootState extends RootState {
+ style: T;
+}
diff --git a/src/plugins/wizard/public/visualizations/vislib/histogram/components/histogram_vis_options.tsx b/src/plugins/wizard/public/visualizations/vislib/histogram/components/histogram_vis_options.tsx
new file mode 100644
index 000000000000..873b26ca4301
--- /dev/null
+++ b/src/plugins/wizard/public/visualizations/vislib/histogram/components/histogram_vis_options.tsx
@@ -0,0 +1,41 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React, { useCallback } from 'react';
+import { i18n } from '@osd/i18n';
+import produce, { Draft } from 'immer';
+import { useTypedDispatch, useTypedSelector } from '../../../../application/utils/state_management';
+import { HistogramOptionsDefaults } from '../histogram_vis_type';
+import { BasicVisOptions } from '../../common/basic_vis_options';
+import { setState } from '../../../../application/utils/state_management/style_slice';
+import { Option } from '../../../../application/app';
+
+function HistogramVisOptions() {
+ const styleState = useTypedSelector((state) => state.style) as HistogramOptionsDefaults;
+ const dispatch = useTypedDispatch();
+
+ const setOption = useCallback(
+ (callback: (draft: Draft) => void) => {
+ const newState = produce(styleState, callback);
+ dispatch(setState(newState));
+ },
+ [dispatch, styleState]
+ );
+
+ return (
+ <>
+
+ >
+ );
+}
+
+export { HistogramVisOptions };
diff --git a/src/plugins/wizard/public/visualizations/vislib/histogram/histogram_vis_type.ts b/src/plugins/wizard/public/visualizations/vislib/histogram/histogram_vis_type.ts
new file mode 100644
index 000000000000..e99f062cb615
--- /dev/null
+++ b/src/plugins/wizard/public/visualizations/vislib/histogram/histogram_vis_type.ts
@@ -0,0 +1,64 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { i18n } from '@osd/i18n';
+import { Schemas } from '../../../../../vis_default_editor/public';
+import { Positions } from '../../../../../vis_type_vislib/public';
+import { AggGroupNames } from '../../../../../data/public';
+import { BasicOptionsDefaults } from '../common/types';
+import { HistogramVisOptions } from './components/histogram_vis_options';
+import { VisualizationTypeOptions } from '../../../services/type_service';
+import { toExpression } from './to_expression';
+
+export interface HistogramOptionsDefaults extends BasicOptionsDefaults {
+ type: 'histogram';
+}
+
+export const createHistogramConfig = (): VisualizationTypeOptions => ({
+ name: 'histogram',
+ title: 'Histogram',
+ icon: 'visBarVertical',
+ description: 'Display histogram visualizations',
+ toExpression,
+ ui: {
+ containerConfig: {
+ data: {
+ schemas: new Schemas([
+ {
+ group: AggGroupNames.Metrics,
+ name: 'metric',
+ title: i18n.translate('visTypeVislib.histogram.metricTitle', {
+ defaultMessage: 'Y-axis',
+ }),
+ min: 1,
+ max: 3,
+ aggFilter: ['!geo_centroid', '!geo_bounds'],
+ defaults: { aggTypes: ['median'] },
+ },
+ {
+ group: AggGroupNames.Buckets,
+ name: 'segment',
+ title: i18n.translate('visTypeVislib.histogram.segmentTitle', {
+ defaultMessage: 'X-axis',
+ }),
+ min: 0,
+ max: 1,
+ aggFilter: ['!geohash_grid', '!geotile_grid', '!filter', '!filters'],
+ defaults: { aggTypes: ['terms'] },
+ },
+ ]),
+ },
+ style: {
+ defaults: {
+ addTooltip: true,
+ addLegend: true,
+ legendPosition: Positions.RIGHT,
+ type: 'histogram',
+ },
+ render: HistogramVisOptions,
+ },
+ },
+ },
+});
diff --git a/src/plugins/wizard/public/visualizations/vislib/histogram/index.ts b/src/plugins/wizard/public/visualizations/vislib/histogram/index.ts
new file mode 100644
index 000000000000..bba280de2d77
--- /dev/null
+++ b/src/plugins/wizard/public/visualizations/vislib/histogram/index.ts
@@ -0,0 +1,6 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+export { createHistogramConfig } from './histogram_vis_type';
diff --git a/src/plugins/wizard/public/visualizations/vislib/histogram/to_expression.ts b/src/plugins/wizard/public/visualizations/vislib/histogram/to_expression.ts
new file mode 100644
index 000000000000..6b6f0fe30baf
--- /dev/null
+++ b/src/plugins/wizard/public/visualizations/vislib/histogram/to_expression.ts
@@ -0,0 +1,46 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { Vis, buildVislibDimensions } from '../../../../../visualizations/public';
+import { buildExpression, buildExpressionFunction } from '../../../../../expressions/public';
+import { HistogramOptionsDefaults } from './histogram_vis_type';
+import { getAggExpressionFunctions } from '../../common/expression_helpers';
+import { VislibRootState } from '../common/types';
+import { getValueAxes } from '../common/get_value_axes';
+
+export const toExpression = async ({
+ style: styleState,
+ visualization,
+}: VislibRootState) => {
+ const { aggConfigs, expressionFns } = await getAggExpressionFunctions(visualization);
+ const { addLegend, addTooltip, legendPosition, type } = styleState;
+ const pipelineConfigs = {
+ // todo: this will blow up for time x dimensions
+ timefilter: null, // todo: get the time filter from elsewhere
+ };
+
+ const vis = new Vis(type);
+ vis.data.aggs = aggConfigs;
+
+ const dimensions = await buildVislibDimensions(vis, pipelineConfigs as any);
+ const valueAxes = getValueAxes(dimensions.y);
+
+ // TODO: what do we want to put in this "vis config"?
+ const visConfig = {
+ addLegend,
+ legendPosition,
+ addTimeMarker: false,
+ addTooltip,
+ dimensions,
+ valueAxes,
+ };
+
+ const vislib = buildExpressionFunction('vislib', {
+ type,
+ visConfig: JSON.stringify(visConfig),
+ });
+
+ return buildExpression([...expressionFns, vislib]).toString();
+};
diff --git a/src/plugins/wizard/public/visualizations/vislib/index.ts b/src/plugins/wizard/public/visualizations/vislib/index.ts
new file mode 100644
index 000000000000..84dc3e346ef5
--- /dev/null
+++ b/src/plugins/wizard/public/visualizations/vislib/index.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+export { createHistogramConfig } from './histogram';
+export { createLineConfig } from './line';
+export { createAreaConfig } from './area';
diff --git a/src/plugins/wizard/public/visualizations/vislib/line/components/line_vis_options.tsx b/src/plugins/wizard/public/visualizations/vislib/line/components/line_vis_options.tsx
new file mode 100644
index 000000000000..a5bb1994c92a
--- /dev/null
+++ b/src/plugins/wizard/public/visualizations/vislib/line/components/line_vis_options.tsx
@@ -0,0 +1,41 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React, { useCallback } from 'react';
+import { i18n } from '@osd/i18n';
+import produce, { Draft } from 'immer';
+import { useTypedDispatch, useTypedSelector } from '../../../../application/utils/state_management';
+import { LineOptionsDefaults } from '../line_vis_type';
+import { setState } from '../../../../application/utils/state_management/style_slice';
+import { Option } from '../../../../application/app';
+import { BasicVisOptions } from '../../common/basic_vis_options';
+
+function LineVisOptions() {
+ const styleState = useTypedSelector((state) => state.style) as LineOptionsDefaults;
+ const dispatch = useTypedDispatch();
+
+ const setOption = useCallback(
+ (callback: (draft: Draft) => void) => {
+ const newState = produce(styleState, callback);
+ dispatch(setState(newState));
+ },
+ [dispatch, styleState]
+ );
+
+ return (
+ <>
+
+ >
+ );
+}
+
+export { LineVisOptions };
diff --git a/src/plugins/wizard/public/visualizations/vislib/line/index.ts b/src/plugins/wizard/public/visualizations/vislib/line/index.ts
new file mode 100644
index 000000000000..721ec7858a7a
--- /dev/null
+++ b/src/plugins/wizard/public/visualizations/vislib/line/index.ts
@@ -0,0 +1,6 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+export { createLineConfig } from './line_vis_type';
diff --git a/src/plugins/wizard/public/visualizations/vislib/line/line_vis_type.ts b/src/plugins/wizard/public/visualizations/vislib/line/line_vis_type.ts
new file mode 100644
index 000000000000..b92019cddd9d
--- /dev/null
+++ b/src/plugins/wizard/public/visualizations/vislib/line/line_vis_type.ts
@@ -0,0 +1,75 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { i18n } from '@osd/i18n';
+import { Schemas } from '../../../../../vis_default_editor/public';
+import { Positions } from '../../../../../vis_type_vislib/public';
+import { AggGroupNames } from '../../../../../data/public';
+import { LineVisOptions } from './components/line_vis_options';
+import { VisualizationTypeOptions } from '../../../services/type_service';
+import { toExpression } from './to_expression';
+import { BasicOptionsDefaults } from '../common/types';
+
+export interface LineOptionsDefaults extends BasicOptionsDefaults {
+ type: 'line';
+}
+
+export const createLineConfig = (): VisualizationTypeOptions => ({
+ name: 'line',
+ title: 'Line',
+ icon: 'visLine',
+ description: 'Display line chart',
+ toExpression,
+ ui: {
+ containerConfig: {
+ data: {
+ schemas: new Schemas([
+ {
+ group: AggGroupNames.Metrics,
+ name: 'metric',
+ title: i18n.translate('visTypeVislib.line.metricTitle', {
+ defaultMessage: 'Y-axis',
+ }),
+ min: 1,
+ max: 3,
+ aggFilter: ['!geo_centroid', '!geo_bounds'],
+ defaults: { aggTypes: ['median'] },
+ },
+ {
+ group: AggGroupNames.Buckets,
+ name: 'segment',
+ title: i18n.translate('visTypeVislib.line.segmentTitle', {
+ defaultMessage: 'X-axis',
+ }),
+ min: 0,
+ max: 1,
+ aggFilter: ['!geohash_grid', '!geotile_grid', '!filter', '!filters'],
+ defaults: { aggTypes: ['terms'] },
+ },
+ {
+ group: AggGroupNames.Metrics,
+ name: 'radius',
+ title: i18n.translate('visTypeVislib.line.radiusTitle', {
+ defaultMessage: 'Dot size',
+ }),
+ min: 0,
+ max: 1,
+ aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality'],
+ defaults: { aggTypes: ['count'] },
+ },
+ ]),
+ },
+ style: {
+ defaults: {
+ addTooltip: true,
+ addLegend: true,
+ legendPosition: Positions.RIGHT,
+ type: 'line',
+ },
+ render: LineVisOptions,
+ },
+ },
+ },
+});
diff --git a/src/plugins/wizard/public/visualizations/vislib/line/to_expression.ts b/src/plugins/wizard/public/visualizations/vislib/line/to_expression.ts
new file mode 100644
index 000000000000..d7c7a13d2be7
--- /dev/null
+++ b/src/plugins/wizard/public/visualizations/vislib/line/to_expression.ts
@@ -0,0 +1,46 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { Vis, buildVislibDimensions } from '../../../../../visualizations/public';
+import { buildExpression, buildExpressionFunction } from '../../../../../expressions/public';
+import { LineOptionsDefaults } from './line_vis_type';
+import { getAggExpressionFunctions } from '../../common/expression_helpers';
+import { VislibRootState } from '../common/types';
+import { getValueAxes } from '../common/get_value_axes';
+
+export const toExpression = async ({
+ style: styleState,
+ visualization,
+}: VislibRootState) => {
+ const { aggConfigs, expressionFns } = await getAggExpressionFunctions(visualization);
+ const { addLegend, addTooltip, legendPosition, type } = styleState;
+ const pipelineConfigs = {
+ // todo: this will blow up for time x dimensions
+ timefilter: null, // todo: get the time filter from elsewhere
+ };
+
+ const vis = new Vis(type);
+ vis.data.aggs = aggConfigs;
+
+ const dimensions = await buildVislibDimensions(vis, pipelineConfigs as any);
+ const valueAxes = getValueAxes(dimensions.y);
+
+ // TODO: what do we want to put in this "vis config"?
+ const visConfig = {
+ addLegend,
+ legendPosition,
+ addTimeMarker: false,
+ addTooltip,
+ dimensions,
+ valueAxes,
+ };
+
+ const vislib = buildExpressionFunction('vislib', {
+ type,
+ visConfig: JSON.stringify(visConfig),
+ });
+
+ return buildExpression([...expressionFns, vislib]).toString();
+};
diff --git a/test/functional/apps/wizard/_base.ts b/test/functional/apps/wizard/_base.ts
index 99cce7fce489..c8940704da5c 100644
--- a/test/functional/apps/wizard/_base.ts
+++ b/test/functional/apps/wizard/_base.ts
@@ -7,7 +7,8 @@ import expect from '@osd/expect';
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getService, getPageObjects }: FtrProviderContext) {
- const PageObjects = getPageObjects(['visualize', 'wizard']);
+ const PageObjects = getPageObjects(['visualize', 'wizard', 'visChart']);
+ const testSubjects = getService('testSubjects');
const log = getService('log');
const retry = getService('retry');
@@ -27,22 +28,33 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
it('should show visualization when a field is added', async () => {
- await PageObjects.wizard.addField('metric', 'Average', 'machine.ram');
- const avgMachineRam = ['13,104,036,080.615', 'Average machine.ram'];
+ const expectedData = [2858, 2904, 2814, 1322, 2784];
+ await PageObjects.wizard.addField('metric', 'Count');
+ await PageObjects.wizard.addField('segment', 'Terms', 'machine.os.raw');
- await retry.try(async function tryingForTime() {
- const metricValue = await PageObjects.wizard.getMetric();
- expect(avgMachineRam).to.eql(metricValue);
- });
+ const data = await PageObjects.visChart.getBarChartData();
+ expect(data).to.eql(expectedData);
});
it('should clear visualization when field is deleted', async () => {
await PageObjects.wizard.removeField('metric', 0);
- await retry.try(async function tryingForTime() {
- const isEmptyWorkspace = await PageObjects.wizard.isEmptyWorkspace();
- expect(isEmptyWorkspace).to.be(true);
- });
+ const isEmptyWorkspace = await PageObjects.wizard.isEmptyWorkspace();
+ expect(isEmptyWorkspace).to.be(true);
+ });
+
+ it('should show warning before changing visualization type', async () => {
+ await PageObjects.wizard.selectVisType('metric', false);
+ const confirmModalExists = await testSubjects.exists('confirmVisChangeModal');
+ expect(confirmModalExists).to.be(true);
+
+ await testSubjects.click('confirmModalCancelButton');
+ });
+
+ it('should change visualization type', async () => {
+ const pickerValue = await PageObjects.wizard.selectVisType('metric');
+
+ expect(pickerValue).to.eql('Metric');
});
});
}
diff --git a/test/functional/page_objects/wizard_page.ts b/test/functional/page_objects/wizard_page.ts
index 08ed41df57d5..bd206f71fa2f 100644
--- a/test/functional/page_objects/wizard_page.ts
+++ b/test/functional/page_objects/wizard_page.ts
@@ -50,10 +50,22 @@ export function WizardPageProvider({ getService, getPageObjects }: FtrProviderCo
return await dataSourceDropdown.getVisibleText();
}
+ public async selectVisType(type: string, confirm = true) {
+ const chartPicker = await testSubjects.find('chartPicker');
+ await chartPicker.click();
+ await testSubjects.click(`visType-${type}`);
+
+ if (confirm) {
+ await testSubjects.click('confirmModalConfirmButton');
+ }
+
+ return chartPicker.getVisibleText();
+ }
+
public async addField(
dropBoxId: string,
aggValue: string,
- fieldValue: string,
+ fieldValue?: string,
returnToMainPanel = true
) {
await testSubjects.click(`dropBoxAddField-${dropBoxId} > dropBoxAddBtn`);
@@ -61,9 +73,12 @@ export function WizardPageProvider({ getService, getPageObjects }: FtrProviderCo
const aggComboBoxElement = await testSubjects.find('defaultEditorAggSelect');
await comboBox.setElement(aggComboBoxElement, aggValue);
await common.sleep(500);
- const fieldComboBoxElement = await testSubjects.find('visDefaultEditorField');
- await comboBox.setElement(fieldComboBoxElement, fieldValue);
- await common.sleep(500);
+
+ if (fieldValue) {
+ const fieldComboBoxElement = await testSubjects.find('visDefaultEditorField');
+ await comboBox.setElement(fieldComboBoxElement, fieldValue);
+ await common.sleep(500);
+ }
if (returnToMainPanel) {
await testSubjects.click('panelCloseBtn');