Skip to content

Commit

Permalink
[D&D] Refactor to use AggService and introduce metric visualization (o…
Browse files Browse the repository at this point in the history
…pensearch-project#1734)

* partial progress

Signed-off-by: Ashwin Pc <ashwinpc@amazon.com>

* simle workign metric using aggShemas

Signed-off-by: Ashwin Pc <ashwinpc@amazon.com>

* updated VisualizationTypeOptions to be a generic

Signed-off-by: Ashwin Pc <ashwinpc@amazon.com>

* partially working metric style options

Signed-off-by: Ashwin Pc <ashwinpc@amazon.com>

* all state objects are serializeable

Signed-off-by: Ashwin Pc <ashwinpc@amazon.com>

* working delete and reorder

Signed-off-by: Ashwin Pc <ashwinpc@amazon.com>

* chore: cleanup agg service changes

Signed-off-by: Ashwin Pc <ashwinpc@amazon.com>
  • Loading branch information
ashwin-pc authored and kavilla committed Jun 27, 2022
1 parent 52f031a commit acd6883
Show file tree
Hide file tree
Showing 41 changed files with 1,223 additions and 636 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const createEditorStateReducer = ({
!state.data.aggs!.aggs.find((agg) => agg.schema === schema.name) && schema.defaults
? (schema as any).defaults.slice(0, schema.max)
: { schema: schema.name };

const aggConfig = state.data.aggs!.createAggConfig(defaultConfig, {
addToAggConfigs: false,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ function MetricVisOptions({
);

const setColorMode: EuiButtonGroupProps['onChange'] = useCallback(
(id) => setMetricValue('metricColorMode', id as ColorModes),
(id: string) => setMetricValue('metricColorMode', id as ColorModes),
[setMetricValue]
);

Expand Down
3 changes: 3 additions & 0 deletions src/plugins/vis_type_metric/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@ import { MetricVisPlugin as Plugin } from './plugin';
export function plugin(initializerContext: PluginInitializerContext) {
return new Plugin(initializerContext);
}

/* Public Types */
export { MetricVisExpressionFunctionDefinition } from './metric_vis_fn';
2 changes: 1 addition & 1 deletion src/plugins/vis_type_metric/public/metric_vis_fn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ interface Arguments {
colorRange: Range[];
font: Style;
metric: any[]; // these aren't typed yet
bucket: any; // these aren't typed yet
bucket?: any; // these aren't typed yet
}

export interface MetricVisRenderValue {
Expand Down
12 changes: 7 additions & 5 deletions src/plugins/visualizations/public/legacy/build_pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,8 @@ export const buildVislibDimensions = async (vis: any, params: BuildPipelineParam
splitColumn: schemas.split_column,
};
if (schemas.segment) {
const xAgg = vis.data.aggs.getResponseAggs()[dimensions.x.accessor];
const a = vis.data.aggs.getResponseAggs();
const xAgg = a[dimensions.x.accessor];
if (xAgg.type.name === 'date_histogram') {
dimensions.x.params.date = true;
const { opensearchUnit, opensearchValue } = xAgg.buckets.getInterval();
Expand Down Expand Up @@ -423,10 +424,10 @@ export const buildPipeline = async (vis: Vis, params: BuildPipelineParams) => {
// request handler
if (vis.type.requestHandler === 'courier') {
pipeline += `opensearchaggs
${prepareString('index', indexPattern!.id)}
metricsAtAllLevels=${vis.isHierarchical()}
partialRows=${vis.params.showPartialRows || false}
${prepareJson('aggConfigs', vis.data.aggs!.aggs)} | `;
${prepareString('index', indexPattern!.id)}
metricsAtAllLevels=${vis.isHierarchical()}
partialRows=${vis.params.showPartialRows || false}
${prepareJson('aggConfigs', vis.data.aggs!.aggs)} | `;
}

const schemas = getSchemas(vis, params);
Expand Down Expand Up @@ -456,5 +457,6 @@ export const buildPipeline = async (vis: Vis, params: BuildPipelineParams) => {
}
}
}

return pipeline;
};
6 changes: 5 additions & 1 deletion src/plugins/wizard/opensearch_dashboards.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@
"ui": true,
"requiredPlugins": [
"navigation",
"charts",
"data",
"opensearchDashboardsReact",
"opensearchDashboardsUtils",
"savedObjects",
"embeddable",
"expressions",
"dashboard",
"visualizations",
"opensearchUiShared"
"opensearchUiShared",
"visDefaultEditor"
],
"optionalPlugins": []
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

.wizSidenav {
@include scrollNavParent(auto 1fr);

grid-area: sideNav;
border-right: $euiBorderThin;
}
Expand All @@ -11,8 +12,13 @@
}

.wizSidenavTabs {
.euiTab__content {
text-transform: capitalize;
}

@include scrollNavParent(min-content 1fr);
&>[role="tabpanel"] {

& > [role="tabpanel"] {
@include scrollNavParent;
}
}
40 changes: 30 additions & 10 deletions src/plugins/wizard/public/application/components/side_nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react
import { WizardServices } from '../../types';
import './side_nav.scss';
import { useTypedDispatch, useTypedSelector } from '../utils/state_management';
import { setIndexPattern } from '../utils/state_management/datasource_slice';
import { setIndexPattern } from '../utils/state_management/visualization_slice';
import { useVisualizationType } from '../utils/use';
import { DataTab } from '../contributions';
import { StyleTabConfig } from '../../services/type_service';

export const SideNav = () => {
const {
Expand All @@ -21,17 +23,32 @@ export const SideNav = () => {
},
} = useOpenSearchDashboards<WizardServices>();
const { IndexPatternSelect } = data.ui;
const { indexPattern } = useTypedSelector((state) => state.dataSource);
const { indexPattern: indexPatternId } = useTypedSelector((state) => state.visualization);
const dispatch = useTypedDispatch();
const {
contributions: { containers },
ui: { containerConfig },
} = useVisualizationType();

const tabs: EuiTabbedContentTab[] = containers.sidePanel.map(({ id, name, Component }) => ({
id,
name,
content: Component,
}));
const tabs: EuiTabbedContentTab[] = Object.entries(containerConfig).map(
([containerName, config]) => {
let content = null;
switch (containerName) {
case 'data':
content = <DataTab key="containerName" />;
break;

case 'style':
content = (config as StyleTabConfig).render();
break;
}

return {
id: containerName,
name: containerName,
content,
};
}
);

return (
<section className="wizSidenav">
Expand All @@ -46,10 +63,13 @@ export const SideNav = () => {
placeholder={i18n.translate('wizard.nav.dataSource.selector.placeholder', {
defaultMessage: 'Select index pattern',
})}
indexPatternId={indexPattern?.id || ''}
indexPatternId={indexPatternId || ''}
onChange={async (newIndexPatternId: any) => {
const newIndexPattern = await data.indexPatterns.get(newIndexPatternId);
dispatch(setIndexPattern(newIndexPattern));

if (newIndexPattern) {
dispatch(setIndexPattern(newIndexPatternId));
}
}}
isClearable={false}
/>
Expand Down
4 changes: 2 additions & 2 deletions src/plugins/wizard/public/application/components/top_nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { getTopNavconfig } from '../utils/get_top_nav_config';
import { WizardServices } from '../../types';

import './top_nav.scss';
import { useTypedSelector } from '../utils/state_management';
import { useIndexPattern } from '../utils/use';

export const TopNav = () => {
const { services } = useOpenSearchDashboards<WizardServices>();
Expand All @@ -22,7 +22,7 @@ export const TopNav = () => {
} = services;

const config = useMemo(() => getTopNavconfig(services), [services]);
const { indexPattern } = useTypedSelector((state) => state.dataSource);
const indexPattern = useIndexPattern();

return (
<div className="wizTopNav">
Expand Down
26 changes: 22 additions & 4 deletions src/plugins/wizard/public/application/components/workspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,34 @@ import {
EuiPanel,
EuiPopover,
} from '@elastic/eui';
import React, { FC, useState, useMemo } from 'react';
import React, { FC, useState, useMemo, useEffect } from 'react';
import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public';
import { WizardServices } from '../../types';
import { useTypedDispatch } from '../utils/state_management';
import { useTypedDispatch, useTypedSelector } from '../utils/state_management';
import { setActiveVisualization } from '../utils/state_management/visualization_slice';
import { useVisualizationType } from '../utils/use';

import './workspace.scss';

export const Workspace: FC = ({ children }) => {
const {
services: {
expressions: { ReactExpressionRenderer },
},
} = useOpenSearchDashboards<WizardServices>();
const { toExpression } = useVisualizationType();
const [expression, setExpression] = useState<string>();
const rootState = useTypedSelector((state) => state);

useEffect(() => {
async function loadExpression() {
const exp = await toExpression(rootState);
setExpression(exp);
}

loadExpression();
}, [rootState, toExpression]);

return (
<section className="wizWorkspace">
<EuiFlexGroup className="wizCanvasControls">
Expand All @@ -32,8 +50,8 @@ export const Workspace: FC = ({ children }) => {
</EuiFlexItem>
</EuiFlexGroup>
<EuiPanel className="wizCanvas">
{children ? (
children
{expression ? (
<ReactExpressionRenderer expression={expression} />
) : (
<EuiFlexItem className="wizWorkspace__empty">
<EuiEmptyPrompt
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import { EuiForm } from '@elastic/eui';
import React, { useMemo } from 'react';
import React, { useMemo, useState } from 'react';
import { useVisualizationType } from '../../../utils/use';
import {
DropboxContribution,
Expand All @@ -20,51 +20,64 @@ import { SelectContribution } from '../common/items';
import { INDEX_FIELD_KEY } from './items/use/use_form_field';
import { DATA_TAB_ID } from '.';
import './config_panel.scss';
import { mapSchemaToAggPanel } from './utils/schema_to_dropbox';
import { useOpenSearchDashboards } from '../../../../../../opensearch_dashboards_react/public';
import { WizardServices } from '../../../../types';
import { SecondaryPanel } from './secondary_panel';

const DEFAULT_ITEMS: MainItemContribution[] = [getTitleContribution()];

export function ConfigPanel() {
const {
contributions: { items },
} = useVisualizationType();
const activeItem = useTypedSelector((state) => state.config.activeItem);
const configItemState = useTypedSelector((state) => state.config.items[activeItem?.id || '']);

const hydratedItems: MainItemContribution[] = [...(items?.[DATA_TAB_ID] ?? []), ...DEFAULT_ITEMS];

const mainPanel = useMemo(() => mapItemToPanelComponents(hydratedItems), [hydratedItems]);
const secondaryPanel = useMemo(() => {
if (!activeItem || !configItemState || typeof configItemState === 'string') return;

// Generate each secondary panel base on active item type
if (activeItem.type === ITEM_TYPES.DROPBOX) {
const activeDropboxContribution = hydratedItems.find(
(item: MainItemContribution) =>
item.type === ITEM_TYPES.DROPBOX && item?.id === activeItem?.id
) as DropboxContribution | undefined;

if (!activeDropboxContribution) return null;

let itemsToRender: SecondaryItemContribution[] = [
getTitleContribution(activeDropboxContribution.label),
getFieldSelectorContribution(),
];

const dropboxFieldInstance = configItemState.instances.find(
({ id }) => id === activeItem.instanceId
);
if (dropboxFieldInstance && dropboxFieldInstance.properties.fieldName) {
itemsToRender = [...itemsToRender, ...activeDropboxContribution.items];
}

return mapItemToPanelComponents(itemsToRender, true);
}
}, [activeItem, configItemState, hydratedItems]);
const vizType = useVisualizationType();
const activeAgg = useTypedSelector((state) => state.visualization.activeVisualization?.activeAgg);
const schemas = vizType.ui.containerConfig.data.schemas;

// TODO: Will cleanup when add and edit field support is re introduced
// const activeItem = useTypedSelector((state) => state.config.activeItem);
// const configItemState = useTypedSelector((state) => state.config.items[activeItem?.id || '']);

// const hydratedItems: MainItemContribution[] = useMemo(
// () => [...(items?.[DATA_TAB_ID] ?? []), ...DEFAULT_ITEMS],
// [items]
// );

// const mainPanel = useMemo(() => mapItemToPanelComponents(hydratedItems), [hydratedItems]);
// const secondaryPanel = useMemo(() => {
// if (!activeItem || !configItemState || typeof configItemState === 'string') return;

// // Generate each secondary panel base on active item type
// if (activeItem.type === ITEM_TYPES.DROPBOX) {
// const activeDropboxContribution = hydratedItems.find(
// (item: MainItemContribution) =>
// item.type === ITEM_TYPES.DROPBOX && item?.id === activeItem?.id
// ) as DropboxContribution | undefined;

// if (!activeDropboxContribution) return null;

// let itemsToRender: SecondaryItemContribution[] = [
// getTitleContribution(activeDropboxContribution.label),
// getFieldSelectorContribution(),
// ];

// const dropboxFieldInstance = configItemState.instances.find(
// ({ id }) => id === activeItem.instanceId
// );
// if (dropboxFieldInstance && dropboxFieldInstance.properties.fieldName) {
// itemsToRender = [...itemsToRender, ...activeDropboxContribution.items];
// }

// return mapItemToPanelComponents(itemsToRender, true);
// }
// }, [activeItem, configItemState, hydratedItems]);

if (!schemas) return null;

const mainPanel = mapSchemaToAggPanel(schemas);

return (
<EuiForm className={`wizConfig ${activeItem ? 'showSecondary' : ''}`}>
<EuiForm className={`wizConfig ${activeAgg ? 'showSecondary' : ''}`}>
<div className="wizConfig__section">{mainPanel}</div>
<div className="wizConfig__section wizConfig--secondary">{secondaryPanel}</div>
<SecondaryPanel />
</EuiForm>
);
}
Expand All @@ -76,16 +89,16 @@ function getTitleContribution(title?: string): TitleItemContribution {
};
}

function getFieldSelectorContribution(): SelectContribution<string> {
return {
type: ItemTypes.SELECT,
id: INDEX_FIELD_KEY,
label: 'Select a Field',
options: (state) => {
return state.dataSource.visualizableFields.map((field) => ({
value: field.name,
inputDisplay: field.displayName,
}));
},
};
}
// function getFieldSelectorContribution(): SelectContribution<string> {
// return {
// type: ItemTypes.SELECT,
// id: INDEX_FIELD_KEY,
// label: 'Select a Field',
// options: (state) => {
// return state.dataSource.visualizableFields.map((field) => ({
// value: field.name,
// inputDisplay: field.displayName,
// }));
// },
// };
// }
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import React from 'react';
import { i18n } from '@osd/i18n';
import { EuiFieldSearch, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { setSearchField } from '../../../utils/state_management/datasource_slice';
import { setSearchField } from '../../../utils/state_management/visualization_slice';
import { useTypedDispatch } from '../../../utils/state_management';

export interface Props {
Expand Down
Loading

0 comments on commit acd6883

Please sign in to comment.