diff --git a/common/constants/explorer.ts b/common/constants/explorer.ts index 6f6d758b08..bd7d3f096c 100644 --- a/common/constants/explorer.ts +++ b/common/constants/explorer.ts @@ -179,6 +179,10 @@ export const DEFAULT_PIE_CHART_PARAMETERS: DefaultPieChartParameterProps = { }; export const GROUPBY = 'dimensions'; export const AGGREGATIONS = 'series'; +export const PARENTFIELDS = 'parentFields'; +export const VALUEFIELD = 'valueField'; +export const CHILDFIELD = 'childField'; +export const TIMESTAMP = 'timestamp'; // stats constants export const STATS_GRID_SPACE_BETWEEN_X_AXIS = 0.01; diff --git a/common/query_manager/ast/builder/stats_builder.ts b/common/query_manager/ast/builder/stats_builder.ts index b4d173f1b0..577ee4c8c8 100644 --- a/common/query_manager/ast/builder/stats_builder.ts +++ b/common/query_manager/ast/builder/stats_builder.ts @@ -26,6 +26,7 @@ import { SpanExpressionChunk, } from '../types'; import { CUSTOM_LABEL } from '../../../../common/constants/explorer'; + export class StatsBuilder implements QueryBuilder { constructor(private statsChunk: statsChunk) {} diff --git a/common/query_manager/ast/expression/AggregateTerm.ts b/common/query_manager/ast/expression/AggregateTerm.ts index ceea30e5fa..9864bbfcd4 100644 --- a/common/query_manager/ast/expression/AggregateTerm.ts +++ b/common/query_manager/ast/expression/AggregateTerm.ts @@ -2,9 +2,8 @@ * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ - -import { PPLNode } from '../node'; import { CUSTOM_LABEL } from '../../../../common/constants/explorer'; +import { PPLNode } from '../node'; export class AggregateTerm extends PPLNode { constructor( name: string, diff --git a/common/query_manager/utils/index.ts b/common/query_manager/utils/index.ts index b5f22013ca..22fc09d78b 100644 --- a/common/query_manager/utils/index.ts +++ b/common/query_manager/utils/index.ts @@ -2,9 +2,8 @@ * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ - -import { AggregationConfigurations, PreviouslyParsedStaleStats } from '../ast/types'; import { CUSTOM_LABEL } from '../../../common/constants/explorer'; +import { AggregationConfigurations, PreviouslyParsedStaleStats } from '../ast/types'; export const composeAggregations = ( aggConfig: AggregationConfigurations, diff --git a/common/types/explorer.ts b/common/types/explorer.ts index 16fe99db03..d9b16733cc 100644 --- a/common/types/explorer.ts +++ b/common/types/explorer.ts @@ -6,6 +6,7 @@ import { History } from 'history'; import Plotly from 'plotly.js-dist'; import { QueryManager } from 'common/query_manager'; +import { VIS_CHART_TYPES } from '../../common/constants/shared'; import { RAW_QUERY, SELECTED_FIELDS, @@ -18,6 +19,7 @@ import { SELECTED_DATE_RANGE, GROUPBY, AGGREGATIONS, + CUSTOM_LABEL, } from '../constants/explorer'; import { CoreStart, @@ -278,7 +280,7 @@ export interface LiveTailProps { export interface ConfigListEntry { label: string; aggregation: string; - custom_label: string; + [CUSTOM_LABEL]: string; name: string; side: string; type: string; @@ -329,3 +331,30 @@ export interface GetTooltipHoverInfoType { tooltipMode: string; tooltipText: string; } + +export interface SelectedConfigItem { + index: number; + name: string; +} + +export interface ParentUnitType { + name: string; + label: string; + type: string; +} + +export interface TreemapParentsProps { + selectedAxis: ParentUnitType[]; + setSelectedParentItem: (item: { isClicked: boolean; index: number }) => void; + handleUpdateParentFields: (arr: ParentUnitType[]) => void; +} + +export interface DataConfigPanelFieldProps { + list: ConfigListEntry[]; + sectionName: string; + visType: VIS_CHART_TYPES; + addButtonText: string; + handleServiceAdd: (name: string) => void; + handleServiceRemove: (index: number, name: string) => void; + handleServiceEdit: (isClose: boolean, arrIndex: number, sectionName: string) => void; +} diff --git a/public/components/event_analytics/explorer/explorer.tsx b/public/components/event_analytics/explorer/explorer.tsx index 241aee7135..27b07c0025 100644 --- a/public/components/event_analytics/explorer/explorer.tsx +++ b/public/components/event_analytics/explorer/explorer.tsx @@ -86,6 +86,7 @@ import { statsChunk, GroupByChunk, StatsAggregationChunk, + GroupField, } from '../../../../common/query_manager/ast/types'; const TYPE_TAB_MAPPING = { @@ -155,7 +156,6 @@ export const Explorer = ({ const [isValidDataConfigOptionSelected, setIsValidDataConfigOptionSelected] = useState( false ); - const queryRef = useRef(); const appBasedRef = useRef(''); appBasedRef.current = appBaseQuery; @@ -316,7 +316,7 @@ export const Explorer = ({ indexPattern: string ): Promise => await timestampUtils.getTimestamp(indexPattern); - const fetchData = async (startingTime?: string, endingTime?: string) => { + const fetchData = async (isRefresh?: boolean, startingTime?: string, endingTime?: string) => { const curQuery = queryRef.current; const rawQueryStr = buildQuery(appBasedRef.current, curQuery![RAW_QUERY]); const curIndex = getIndexPatternFromRawQuery(rawQueryStr); @@ -375,10 +375,7 @@ export const Explorer = ({ changeVisualizationConfig({ tabId, vizId: visId, - data: { - ...userVizConfigs[visId], - dataConfig: {}, - }, + data: isRefresh ? { dataConfig: {} } : { ...userVizConfigs[visId] }, }) ); } @@ -932,11 +929,12 @@ export const Explorer = ({ label: agg.function?.value_expression, name: agg.function?.value_expression, aggregation: agg.function?.name, - [CUSTOM_LABEL]: agg[CUSTOM_LABEL], + [CUSTOM_LABEL]: agg[CUSTOM_LABEL as keyof StatsAggregationChunk], })), [GROUPBY]: groupByToken?.group_fields?.map((agg) => ({ label: agg.name ?? '', name: agg.name ?? '', + [CUSTOM_LABEL]: agg[CUSTOM_LABEL as keyof GroupField] ?? '', })), span, }; @@ -956,7 +954,7 @@ export const Explorer = ({ if (availability !== true) { await updateQueryInStore(tempQuery); } - await fetchData(); + await fetchData(true); if (selectedContentTabId === TAB_CHART_ID) { // parse stats section on every search @@ -966,7 +964,7 @@ export const Explorer = ({ changeVizConfig({ tabId, vizId: curVisId, - data: { ...updatedDataConfig }, + data: { dataConfig: { ...updatedDataConfig } }, }) ); } @@ -1269,7 +1267,7 @@ export const Explorer = ({ const handleLiveTailSearch = useCallback( async (startingTime: string, endingTime: string) => { await updateQueryInStore(tempQuery); - fetchData(startingTime, endingTime); + fetchData(false, startingTime, endingTime); }, [tempQuery] ); diff --git a/public/components/event_analytics/explorer/visualizations/config_panel/config_panel.scss b/public/components/event_analytics/explorer/visualizations/config_panel/config_panel.scss index 214bed8d42..a919709f5e 100644 --- a/public/components/event_analytics/explorer/visualizations/config_panel/config_panel.scss +++ b/public/components/event_analytics/explorer/visualizations/config_panel/config_panel.scss @@ -4,7 +4,7 @@ */ .lnsConfigPanel__addLayerBtn { - color: transparentize($euiColorMediumShade, .3); + color: transparentize($euiColorMediumShade, 0.3); // Remove EuiButton's default shadow to make button more subtle // sass-lint:disable-block no-important box-shadow: none !important; @@ -21,7 +21,7 @@ $vis-editor-sidebar-min-width: 350px; min-width: $vis-editor-sidebar-min-width; // a hack for IE, issue: https://github.com/elastic/kibana/issues/66586 - > .visEditorSidebar__formWrapper { + >.visEditorSidebar__formWrapper { flex-basis: auto; } } @@ -66,7 +66,7 @@ $vis-editor-sidebar-min-width: 350px; padding: $euiSizeS; border-radius: $euiBorderRadius; - + .visEditorSidebar__section { + +.visEditorSidebar__section { margin-top: $euiSizeS; } } @@ -128,7 +128,7 @@ $vis-editor-sidebar-min-width: 350px; max-height: 250px; } -.euiComboBoxOptionsList__rowWrap > div { +.euiComboBoxOptionsList__rowWrap>div { height: 250px !important; } @@ -165,3 +165,27 @@ $vis-editor-sidebar-min-width: 350px; overflow-y: unset; // unset default setting } +.panelItem_button { + display: grid; + grid-template-columns: 1fr auto; + grid-gap: $euiSizeS; + padding: $euiSizeS $euiSizeM; + align-items: center; +} + +.field_text { + text-overflow: ellipsis; + overflow: hidden; +} + +.panel_section { + border-bottom: 1px solid #d3dae6; +} + +.panel_title { + display: block; +} + +.panel_title:first-letter { + text-transform: uppercase; +} diff --git a/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_style_slider.tsx b/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_style_slider.tsx index c90fd41803..b1f9d890ba 100644 --- a/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_style_slider.tsx +++ b/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_style_slider.tsx @@ -4,45 +4,54 @@ */ import React, { ReactNode } from 'react'; -import { EuiTitle, EuiSpacer, EuiRange, htmlIdGenerator, } from '@elastic/eui'; +import { EuiTitle, EuiSpacer, EuiRange, htmlIdGenerator } from '@elastic/eui'; export interface EuiRangeTick { - value: number; - label: ReactNode; + value: number; + label: ReactNode; } interface Props { - title: string; - currentRange: string; - minRange?: number; - maxRange: number; - showTicks?: boolean; - ticks?: EuiRangeTick[]; - step: number; - handleSliderChange: (e: React.ChangeEvent | React.MouseEvent) => void; + title: string; + currentRange: string; + minRange?: number; + maxRange: number; + showTicks?: boolean; + ticks?: EuiRangeTick[]; + step: number; + handleSliderChange: ( + e: React.ChangeEvent | React.MouseEvent + ) => void; } export const SliderConfig: React.FC = ({ - title, currentRange, handleSliderChange, minRange, maxRange, showTicks, ticks, step + title, + currentRange, + handleSliderChange, + minRange, + maxRange, + showTicks, + ticks, + step, }) => ( - <> - -

{title}

-
- - handleSliderChange(e.target.value)} - showTicks={showTicks} - ticks={ticks} - step={step} - compressed - showInput - /> - + <> + +

{title}

+
+ + handleSliderChange(e.target.value)} + showTicks={showTicks} + ticks={ticks} + step={step} + compressed + showInput + /> + ); diff --git a/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_treemap_parents.tsx b/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_treemap_parents.tsx index 26e6e4a97f..12b48a7235 100644 --- a/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_treemap_parents.tsx +++ b/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_treemap_parents.tsx @@ -3,34 +3,17 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { EuiButtonIcon, EuiLink, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui'; +import { isEmpty } from 'lodash'; import React, { Fragment } from 'react'; -import { - EuiButton, - EuiFormRow, - EuiSpacer, - EuiIcon, - EuiComboBox, - EuiComboBoxOptionOption, - EuiText, -} from '@elastic/eui'; -import { isEmpty, uniqueId } from 'lodash'; - -export interface ParentUnitType { - name: string; - label: string; - type: string; -} - -export const ConfigTreemapParentFields = ({ dropdownList, selectedAxis, onSelectChange }: any) => { - const addButtonText = '+ Add Parent'; - - const options = dropdownList.map((item) => { - return { - ...item, - label: item.name, - }; - }); +import { ParentUnitType, TreemapParentsProps } from '../../../../../../../../common/types/explorer'; +export const ConfigTreemapParentFields = ({ + selectedAxis, + setSelectedParentItem, + handleUpdateParentFields, +}: TreemapParentsProps) => { + const addButtonText = 'Add Parent'; const initialParentState = { name: '', label: '', @@ -38,19 +21,17 @@ export const ConfigTreemapParentFields = ({ dropdownList, selectedAxis, onSelect }; const handleAddParent = () => { - onSelectChange([...selectedAxis, initialParentState]); + const arr = [...selectedAxis, initialParentState]; + handleUpdateParentFields(arr); + setSelectedParentItem({ index: arr.length - 1, isClicked: true }); }; - const handleParentChange = (options: EuiComboBoxOptionOption[], index: number) => { - onSelectChange([ - ...selectedAxis.slice(0, index), - (options[0] as ParentUnitType) ?? initialParentState, - ...selectedAxis.slice(index + 1, selectedAxis.length), - ]); + const handleEditParent = (index: number) => { + setSelectedParentItem({ index, isClicked: true }); }; const handleParentDelete = (index: number) => { - onSelectChange([ + handleUpdateParentFields([ ...selectedAxis.slice(0, index), ...selectedAxis.slice(index + 1, selectedAxis.length), ]); @@ -59,41 +40,38 @@ export const ConfigTreemapParentFields = ({ dropdownList, selectedAxis, onSelect return ( <> {!isEmpty(selectedAxis) && - selectedAxis.map((_, index: number) => { + selectedAxis.map((obj: ParentUnitType, index: number) => { return ( - - handleParentDelete(index)} - /> - - } - > - handleParentChange(options, index)} - aria-label="Use aria labels when no actual label is in use" + + + handleEditParent(index)}> + {obj.label !== '' ? obj.label : `Parent ${index + 1}`} + + + handleParentDelete(index)} /> - + ); })} - - - {addButtonText} - + + {addButtonText} + + ); }; diff --git a/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_config_item_click_panel.tsx b/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_config_item_click_panel.tsx new file mode 100644 index 0000000000..25e3a58b46 --- /dev/null +++ b/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_config_item_click_panel.tsx @@ -0,0 +1,37 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +import React from 'react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiIcon, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; +export interface TitleProps { + title: string; + isSecondary?: boolean; + closeMenu?: () => void; +} + +export const DataConfigItemClickPanel = ({ title, isSecondary, closeMenu }: TitleProps) => { + const icon = isSecondary && ( + + ); + return ( + <> + + {icon && {icon}} + + +

{title}

+
+
+
+ {isSecondary ? : } + + ); +}; diff --git a/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_config_panel_fields.tsx b/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_config_panel_fields.tsx new file mode 100644 index 0000000000..5f29f73e40 --- /dev/null +++ b/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_config_panel_fields.tsx @@ -0,0 +1,111 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +import React, { Fragment } from 'react'; +import { + EuiButtonIcon, + EuiIcon, + EuiLink, + EuiPanel, + EuiSpacer, + EuiText, + EuiTitle, + EuiToolTip, +} from '@elastic/eui'; +import { isArray } from 'lodash'; +import { + ConfigListEntry, + DataConfigPanelFieldProps, +} from '../../../../../../../../common/types/explorer'; +import { VIS_CHART_TYPES } from '../../../../../../../../common/constants/shared'; +import { + AGGREGATIONS, + CUSTOM_LABEL, + GROUPBY, +} from '../../../../../../../../common/constants/explorer'; + +export const DataConfigPanelFields = ({ + list, + sectionName, + visType, + addButtonText, + handleServiceAdd, + handleServiceRemove, + handleServiceEdit, +}: DataConfigPanelFieldProps) => { + const isAggregation = sectionName === AGGREGATIONS; + const isHeatMapAddButton = (name: string) => { + if (!list || !isArray(list) || visType !== VIS_CHART_TYPES.HeatMap) return false; + return name === AGGREGATIONS ? list.length >= 1 : list.length >= 2; + }; + + const tooltipIcon = ; + const crossIcon = (index: number) => ( + handleServiceRemove(index, sectionName)} + /> + ); + + const aggregationToolTip = (iconToDisplay: JSX.Element) => ( + + {iconToDisplay} + + ); + + return ( +
+
+ +

{sectionName}

+
+ {isAggregation && aggregationToolTip(tooltipIcon)} +
+ + {isArray(list) && + list.map((obj: ConfigListEntry, index: number) => ( + + + + handleServiceEdit(false, index, sectionName)} + > + {obj[CUSTOM_LABEL] || `${isAggregation ? obj.aggregation : ''} ${obj.label}`} + + + {isAggregation ? aggregationToolTip(crossIcon(index)) : crossIcon(index)} + + + + ))} + {!isHeatMapAddButton(sectionName) && ( + + {addButtonText} + handleServiceAdd(sectionName)} + /> + + )} + +
+ ); +}; diff --git a/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_configurations_panel.tsx b/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_configurations_panel.tsx index 1914ba7bbd..7dcaa36cf9 100644 --- a/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_configurations_panel.tsx +++ b/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_configurations_panel.tsx @@ -5,39 +5,44 @@ import './data_configurations_panel.scss'; -import React, { useEffect, useState, useContext, useCallback, useMemo } from 'react'; +import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react'; import { some } from 'lodash'; import { - EuiTitle, - EuiComboBox, - EuiSpacer, EuiButton, + EuiComboBox, + EuiFieldNumber, EuiFieldText, EuiFlexItem, EuiFormRow, - EuiIcon, EuiPanel, - EuiText, - EuiFieldNumber, + EuiSpacer, + EuiTitle, htmlIdGenerator, - EuiToolTip, } from '@elastic/eui'; -import { useDispatch, batch } from 'react-redux'; -import { changeQuery } from '../../../../../redux/slices/query_slice'; -import { change as changeVizConfig } from '../../../../../redux/slices/viualization_config_slice'; +import { batch, useDispatch } from 'react-redux'; import { AGGREGATIONS, AGGREGATION_OPTIONS, + CUSTOM_LABEL, GROUPBY, RAW_QUERY, + TIMESTAMP, TIME_INTERVAL_OPTIONS, - CUSTOM_LABEL, } from '../../../../../../../../common/constants/explorer'; -import { ButtonGroupItem } from './config_button_group'; import { VIS_CHART_TYPES } from '../../../../../../../../common/constants/shared'; -import { ConfigList, DataConfigPanelProps } from '../../../../../../../../common/types/explorer'; -import { TabContext } from '../../../../../hooks'; import { composeAggregations } from '../../../../../../../../common/query_manager/utils'; +import { + ConfigList, + ConfigListEntry, + DataConfigPanelFieldProps, + DataConfigPanelProps, +} from '../../../../../../../../common/types/explorer'; +import { TabContext } from '../../../../../hooks'; +import { changeQuery } from '../../../../../redux/slices/query_slice'; +import { change as changeVizConfig } from '../../../../../redux/slices/viualization_config_slice'; +import { DataConfigItemClickPanel } from '../config_controls/data_config_item_click_panel'; +import { DataConfigPanelFields } from '../config_controls/data_config_panel_fields'; +import { ButtonGroupItem } from './config_button_group'; const initialDimensionEntry = { label: '', @@ -64,6 +69,11 @@ export const DataConfigPanelItem = ({ indexFields: { availableFields }, } = data; const [configList, setConfigList] = useState({}); + const [isAddConfigClicked, setIsAddConfigClicked] = useState(false); + const [selectedConfigItem, setSelectedConfigItem] = useState({ + index: -1, + name: '', + }); const { userConfigs } = data; useEffect(() => { @@ -71,62 +81,25 @@ export const DataConfigPanelItem = ({ setConfigList({ ...userConfigs.dataConfig, }); - } else if (some(SPECIAL_RENDERING_VIZS, (visType) => visType === visualizations.vis.name)) { - // any vis that doesn't conform normal metrics/dimensions data confiurations - setConfigList({ - ...DEFAULT_DATA_CONFIGS[visualizations.vis.name], - }); - } else { - // default - const statsTokens = queryManager.queryParser().parse(data.query.rawQuery).getStats(); - if (!statsTokens) { - setConfigList({ - metrics: [], - dimensions: [], - }); - } else { - const fieldInfo = statsTokens.groupby?.span?.span_expression?.field; - setConfigList({ - metrics: statsTokens.aggregations.map((agg) => ({ - alias: agg.alias, - label: agg.function?.value_expression, - name: agg.function?.value_expression, - aggregation: agg.function?.name, - })), - dimensions: statsTokens.groupby?.group_fields?.map((agg) => ({ - label: agg.name ?? '', - name: agg.name ?? '', - })), - span: { - time_field: statsTokens.groupby?.span?.span_expression?.field - ? [getStandardedOuiField(fieldInfo, 'timestamp')] - : [], - interval: statsTokens.groupby?.span?.span_expression?.literal_value ?? '0', - unit: statsTokens.groupby?.span?.span_expression?.time_unit - ? [getStandardedOuiField(statsTokens.groupby?.span?.span_expression?.time_unit)] - : [], - }, - }); - } } }, [userConfigs?.dataConfig, visualizations.vis.name]); - const updateList = (value: string, index: number, name: string, field: string) => { - const list = { ...configList }; - let listItem = { ...list[name][index] }; + const updateList = (value: string, field: string) => { + const { index, name } = selectedConfigItem; + let listItem = { ...configList[name][index] }; listItem = { ...listItem, - [field]: value, + [field]: field === 'custom_label' ? value.trim() : value, }; if (field === 'label') { listItem.name = value; } const updatedList = { - ...list, + ...configList, [name]: [ - ...list[name].slice(0, index), + ...configList[name].slice(0, index), listItem, - ...list[name].slice(index + 1, list[name].length), + ...configList[name].slice(index + 1, configList[name].length), ], }; setConfigList(updatedList); @@ -152,6 +125,7 @@ export const DataConfigPanelItem = ({ }; const handleServiceAdd = (name: string) => { + setIsAddConfigClicked(true); const list = { ...configList, [name]: [ @@ -159,10 +133,28 @@ export const DataConfigPanelItem = ({ name === AGGREGATIONS ? initialSeriesEntry : initialDimensionEntry, ], }; + setSelectedConfigItem({ index: list[name].length - 1, name }); setConfigList(list); }; - const updateChart = (updatedConfigList: ConfigList = configList) => { + const handleServiceEdit = (isClose: boolean, arrIndex: number, sectionName: string) => { + if (isClose) { + const { index, name } = selectedConfigItem; + const selectedObj = configList[name][index]; + if ( + selectedObj.aggregation !== 'count' && + (selectedObj.aggregation === '' || selectedObj.name === '') + ) { + const list = { ...configList }; + list[name].splice(index, 1); + setConfigList(list); + } + } + setSelectedConfigItem({ index: arrIndex, name: sectionName }); + setIsAddConfigClicked(!isClose); + }; + + const updateChart = (updatedConfigList = configList) => { if (visualizations.vis.name === VIS_CHART_TYPES.Histogram) { dispatch( changeVizConfig({ @@ -172,8 +164,8 @@ export const DataConfigPanelItem = ({ ...userConfigs, dataConfig: { ...userConfigs.dataConfig, - [GROUPBY]: configList[GROUPBY], - [AGGREGATIONS]: configList[AGGREGATIONS], + [GROUPBY]: updatedConfigList[GROUPBY], + [AGGREGATIONS]: updatedConfigList[AGGREGATIONS], }, }, }) @@ -195,7 +187,7 @@ export const DataConfigPanelItem = ({ }, }) ); - await fetchData(); + await fetchData(false); await dispatch( changeVizConfig({ tabId, @@ -203,8 +195,8 @@ export const DataConfigPanelItem = ({ data: { dataConfig: { ...userConfigs.dataConfig, - [GROUPBY]: configList[GROUPBY], - [AGGREGATIONS]: configList[AGGREGATIONS], + [GROUPBY]: updatedConfigList[GROUPBY], + [AGGREGATIONS]: updatedConfigList[AGGREGATIONS], breakdowns: updatedConfigList.breakdowns, span: updatedConfigList.span, }, @@ -231,142 +223,99 @@ export const DataConfigPanelItem = ({ : unselectedFields; }; - const getCommonUI = (lists, sectionName: string) => { + const getCommonUI = (title: string) => { + const { index, name } = selectedConfigItem; + const selectedObj = configList[name][index]; + const isDimensions = name === GROUPBY; return ( <> - {Array.isArray(lists) && - lists.map((singleField, index: number) => ( - <> -
-
- {sectionName === GROUPBY && visualizations.vis.name === VIS_CHART_TYPES.HeatMap && ( - -
{index === 0 ? 'X-Axis' : 'Y-Axis'}
-
- )} - - {sectionName === AGGREGATIONS && ( - - - handleServiceRemove(index, sectionName)} - /> - - - ) - } - > - - updateList( - e.length > 0 ? e[0].label : '', - index, - sectionName, - 'aggregation' - ) - } - /> - - )} - - handleServiceRemove(index, sectionName)} - /> - - ) - } - > - - updateList(e.length > 0 ? e[0].label : '', index, sectionName, 'label') - } - /> - - - - updateList(e.target.value, index, sectionName, CUSTOM_LABEL) - } - aria-label="Use aria labels when no actual label is in use" - /> - - - {isPositionButtonVisible(sectionName) && ( - - - updateList(id, index, sectionName, 'side') - } - /> - - )} - - -
-
- - - ))} - {visualizations.vis.name !== VIS_CHART_TYPES.HeatMap && ( - - handleServiceAdd(sectionName)} - disabled={ - sectionName === 'dimensions' && - (visualizations.vis.name === VIS_CHART_TYPES.Line || - visualizations.vis.name === VIS_CHART_TYPES.Scatter) - } - > - Add - - - )} +
+
+ handleServiceEdit(true, -1, '')} + /> + + {/* Aggregation input for Series */} + {!isDimensions && ( + + updateList(e.length > 0 ? e[0].label : '', 'aggregation')} + /> + + )} + {/* Show input fields for Series when aggregation is not empty */} + {!isDimensions && selectedObj.aggregation !== '' && ( + <> + {getCommonDimensionsField(selectedObj, name)} + + updateList(e.target.value, CUSTOM_LABEL)} + aria-label="input label" + /> + + + )} + {/* Show input fields for dimensions */} + {isDimensions && getCommonDimensionsField(selectedObj, name)} + {isPositionButtonVisible(name) && ( + + updateList(id, 'side')} + /> + + )} + + +
+
); }; + const getCommonDimensionsField = (selectedObj: ConfigListEntry, name: string) => ( + + updateList(e.length > 0 ? e[0].label : '', 'label')} + /> + + ); + const getNumberField = (type: string) => ( <> updateHistogramConfig('dimensions', type, e.target.value)} + onChange={(e) => updateHistogramConfig(GROUPBY, type, e.target.value)} data-test-subj="valueFieldNumber" /> @@ -392,7 +341,7 @@ export const DataConfigPanelItem = ({ [configList[GROUPBY]] ); - const Breakdowns = useMemo(() => { + const Breakdowns = () => { return ( <>
@@ -420,7 +369,7 @@ export const DataConfigPanelItem = ({
); - }, [configList[GROUPBY], configList.breakdowns]); + }; const DateHistogram = useMemo(() => { return ( @@ -430,11 +379,11 @@ export const DataConfigPanelItem = ({ idxField.type === 'timestamp') + .filter((idxField) => idxField.type === TIMESTAMP) .map((field) => ({ ...field, label: field.name }))} selectedOptions={ configList.span?.time_field ? [...configList.span?.time_field] : [] @@ -469,12 +418,12 @@ export const DataConfigPanelItem = ({ }; }); }} - aria-label="Use aria labels when no actual label is in use" + aria-label="interval field" /> { @@ -504,7 +453,20 @@ export const DataConfigPanelItem = ({ ); }, [availableFields, configList.span]); - return ( + const getRenderFieldsObj = (sectionName: string): DataConfigPanelFieldProps => { + return { + list: configList[sectionName], + sectionName, + visType: visualizations.vis.name, + addButtonText: 'Click to add', + handleServiceAdd, + handleServiceRemove, + handleServiceEdit, + }; + }; + return isAddConfigClicked ? ( + getCommonUI(selectedConfigItem.name) + ) : ( <>

Configuration

@@ -512,27 +474,9 @@ export const DataConfigPanelItem = ({ {visualizations.vis.name !== VIS_CHART_TYPES.Histogram ? ( <> -
- -

Series

-
- - - -
- - {getCommonUI(configList[AGGREGATIONS], AGGREGATIONS)} - - -

Dimensions

-
+ {DataConfigPanelFields(getRenderFieldsObj(AGGREGATIONS))} - {getCommonUI(configList[GROUPBY], GROUPBY)} + {DataConfigPanelFields(getRenderFieldsObj(GROUPBY))}

Date Histogram

diff --git a/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/treemap_config_panel_item.tsx b/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/treemap_config_panel_item.tsx index e141dec27e..2870fe90f2 100644 --- a/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/treemap_config_panel_item.tsx +++ b/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/treemap_config_panel_item.tsx @@ -3,45 +3,65 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useEffect, useState, useContext } from 'react'; import { - EuiTitle, - EuiComboBox, - EuiSpacer, EuiButton, + EuiComboBox, + EuiComboBoxOptionOption, EuiFlexItem, EuiFormRow, EuiPanel, + EuiSpacer, + EuiTitle, + htmlIdGenerator, } from '@elastic/eui'; +import { uniqueId } from 'lodash'; +import React, { useContext, useEffect, useState } from 'react'; import { useDispatch } from 'react-redux'; -import { ConfigTreemapParentFields } from './config_treemap_parents'; +import { ActionCreatorWithPayload } from '@reduxjs/toolkit'; import { + CHILDFIELD, AGGREGATIONS, GROUPBY, NUMERICAL_TYPES, + PARENTFIELDS, + VALUEFIELD, } from '../../../../../../../../common/constants/explorer'; -import { DataConfigPanelProps } from '../../../../../../../../common/types/explorer'; import { TabContext } from '../../../../../hooks'; +import { ConfigTreemapParentFields } from './config_treemap_parents'; +import { DataConfigItemClickPanel } from './data_config_item_click_panel'; +import { + DataConfigPanelProps, + ParentUnitType, +} from '../../../../../../../../common/types/explorer'; +import { VIS_CHART_TYPES } from '../../../../../../../../common/constants/shared'; export const TreemapConfigPanelItem = ({ fieldOptionList, visualizations, }: DataConfigPanelProps) => { const dispatch = useDispatch(); - const { tabId, curVisId, changeVisualizationConfig, fetchData, handleQueryChange } = useContext< - any - >(TabContext); + const { tabId, curVisId, changeVisualizationConfig } = useContext<{ + [key: string]: string | VIS_CHART_TYPES | ActionCreatorWithPayload; + }>(TabContext); const { data } = visualizations; const { userConfigs } = data; const { data: vizData = {}, metadata: { fields = [] } = {} } = data?.rawVizData; const newEntry = { label: '', name: '' }; - + const initialParentState = { + name: '', + label: '', + type: '', + }; const [configList, setConfigList] = useState({ [GROUPBY]: [{ childField: { ...newEntry }, parentFields: [] }], [AGGREGATIONS]: [{ valueField: { ...newEntry } }], }); + const [selectedParentItem, setSelectedParentItem] = useState<{ + isClicked: boolean; + index: number; + }>({ isClicked: false, index: -1 }); useEffect(() => { if (userConfigs && userConfigs.dataConfig) { @@ -52,8 +72,7 @@ export const TreemapConfigPanelItem = ({ }, [userConfigs?.dataConfig, visualizations.vis.name]); const updateList = (configName: string, fieldName: string, value: string | any[]) => { - const list = { ...configList }; - let listItem = { ...list[configName][0] }; + let listItem = { ...configList[configName][0] }; const newField = { label: value, @@ -62,7 +81,7 @@ export const TreemapConfigPanelItem = ({ }; listItem = { ...listItem, [fieldName]: typeof value === 'string' ? newField : value }; const newList = { - ...list, + ...configList, [configName]: [listItem], }; setConfigList(newList); @@ -87,11 +106,11 @@ export const TreemapConfigPanelItem = ({ const getOptionsAvailable = (sectionName: string) => { const { dimensions, series } = configList; - let selectedFields = {}; + const selectedFields = {}; let allSelectedFields = []; for (const key in configList) { - if (key === 'dimensions') { + if (key === GROUPBY) { const [dimElements] = dimensions; const { childField, parentFields } = dimElements; allSelectedFields = [childField, ...parentFields]; @@ -110,7 +129,76 @@ export const TreemapConfigPanelItem = ({ : unselectedFields; }; - return ( + const options = getOptionsAvailable(GROUPBY).map((opt) => ({ + label: opt.label, + name: opt.name, + })); + + const renderParentPanel = () => { + const selectedAxis = configList.dimensions[0]?.parentFields; + const { index } = selectedParentItem; + return ( + <> + isHandlePanelClickBack(selectedAxis)} + /> + + + ); + }; + + /** + * Update DataConfiguration of parent fields list. + * @param arr list to be updated + */ + const handleUpdateParentFields = (arr: ParentUnitType[]) => + updateList(GROUPBY, PARENTFIELDS, arr); + + /** + * function changes the value in parent input field. + * @param value updated value + */ + const handleParentChange = (values: Array>) => { + const selectedAxis = configList.dimensions[0]?.parentFields; + const { index } = selectedParentItem; + const val = [ + ...selectedAxis.slice(0, index), + (values[0] as ParentUnitType) ?? initialParentState, + ...selectedAxis.slice(index + 1, selectedAxis.length), + ]; + handleUpdateParentFields(val); + }; + + /** + * Changes the array when back button in Data Config panel is clicked. + * @param parentArray updated array of parent fields. + */ + const isHandlePanelClickBack = (parentArray: ParentUnitType[]) => { + const { index } = selectedParentItem; + if (parentArray[index].name === '') { + const arr = [ + ...parentArray.slice(0, index), + ...parentArray.slice(index + 1, parentArray.length), + ]; + handleUpdateParentFields(arr); + } + setSelectedParentItem({ isClicked: false, index: -1 }); + }; + + return selectedParentItem.isClicked ? ( + renderParentPanel() + ) : (

Data Configurations

@@ -132,23 +220,24 @@ export const TreemapConfigPanelItem = ({ } singleSelection={{ asPlainText: true }} onChange={(val) => - updateList(GROUPBY, 'childField', val.length > 0 ? val[0].label : '') + updateList(GROUPBY, CHILDFIELD, val.length > 0 ? val[0].label : '') } /> + +

Parent Fields

+
({ - label: opt.label, - name: opt.label, - }))} selectedAxis={configList.dimensions[0]?.parentFields} - onSelectChange={(val) => updateList(GROUPBY, 'parentFields', val)} + handleUpdateParentFields={handleUpdateParentFields} + setSelectedParentItem={setSelectedParentItem} />
+

Metrics

@@ -165,7 +254,7 @@ export const TreemapConfigPanelItem = ({ } singleSelection={{ asPlainText: true }} onChange={(val) => - updateList(AGGREGATIONS, 'valueField', val.length > 0 ? val[0].label : '') + updateList(AGGREGATIONS, VALUEFIELD, val.length > 0 ? val[0].label : '') } />
diff --git a/public/components/visualizations/charts/bar/bar.tsx b/public/components/visualizations/charts/bar/bar.tsx index 32c091dca6..a892822523 100644 --- a/public/components/visualizations/charts/bar/bar.tsx +++ b/public/components/visualizations/charts/bar/bar.tsx @@ -13,7 +13,7 @@ import { } from '../../../../../common/constants/shared'; import { AvailabilityUnitType } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_availability'; import { ThresholdUnitType } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_thresholds'; -import { hexToRgb } from '../../../event_analytics/utils/utils'; +import { getPropName, hexToRgb } from '../../../event_analytics/utils/utils'; import { EmptyPlaceholder } from '../../../event_analytics/explorer/visualizations/shared_components/empty_placeholder'; import { AGGREGATIONS, GROUPBY } from '../../../../../common/constants/explorer'; import { IVisualizationContainerProps } from '../../../../../common/types/explorer'; @@ -70,7 +70,9 @@ export const Bar = ({ visualizations, layout, config }: any) => { ?.color) || PLOTLY_COLOR[index % PLOTLY_COLOR.length]; - let bars, valueSeries, valueForXSeries; + let bars; + let valueSeries; + let valueForXSeries; /** * determine x axis @@ -106,8 +108,8 @@ export const Bar = ({ visualizations, layout, config }: any) => { * prepare data for visualization, map x-xais to y-xais */ const chartAxis = useMemo(() => { - return yaxes[0] && Array.isArray(queriedVizData[`${yaxes[0].aggregation}(${yaxes[0].name})`]) - ? queriedVizData[`${yaxes[0].aggregation}(${yaxes[0].name})`].map((_, idx) => { + return Array.isArray(queriedVizData[getPropName(yaxes[0])]) + ? queriedVizData[getPropName(yaxes[0])].map((_, idx) => { // let combineXaxis = ''; const xaxisName = xaxes.map((xaxis) => { return queriedVizData[xaxis.name] && queriedVizData[xaxis.name][idx] @@ -121,8 +123,8 @@ export const Bar = ({ visualizations, layout, config }: any) => { bars = yaxes?.map((yMetric, idx) => { return { - y: isVertical ? queriedVizData[`${yMetric.aggregation}(${yMetric.name})`] : chartAxis, - x: isVertical ? chartAxis : queriedVizData[`${yMetric.aggregation}(${yMetric.name})`], + y: isVertical ? queriedVizData[getPropName(yMetric)] : chartAxis, + x: isVertical ? chartAxis : queriedVizData[getPropName(yMetric)], type: visMetaData.type, marker: { color: getSelectedColorTheme(yMetric, idx), @@ -131,7 +133,7 @@ export const Bar = ({ visualizations, layout, config }: any) => { width: lineWidth, }, }, - name: yMetric.name, + name: getPropName(yMetric), orientation: barOrientation, }; }); diff --git a/public/components/visualizations/charts/helpers/viz_types.ts b/public/components/visualizations/charts/helpers/viz_types.ts index df9822cba3..2820a72af1 100644 --- a/public/components/visualizations/charts/helpers/viz_types.ts +++ b/public/components/visualizations/charts/helpers/viz_types.ts @@ -15,9 +15,10 @@ import { VIS_CHART_TYPES } from '../../../../../common/constants/shared'; import { QueryManager } from '../../../../../common/query_manager'; import { AGGREGATIONS, + CUSTOM_LABEL, GROUPBY, + PARENTFIELDS, TIME_INTERVAL_OPTIONS, - CUSTOM_LABEL, } from '../../../../../common/constants/explorer'; interface IVizContainerProps { vizId: string; @@ -140,8 +141,8 @@ const defaultUserConfigs = (queryString, visualizationName: string) => { } else if (visualizationName === VIS_CHART_TYPES.HeatMap) { tempUserConfigs = { ...tempUserConfigs, - [GROUPBY]: [initialDimensionEntry, initialDimensionEntry], - [AGGREGATIONS]: [initialSeriesEntry], + [GROUPBY]: [], + [AGGREGATIONS]: [], }; } else { tempUserConfigs = { @@ -175,10 +176,12 @@ const getUserConfigs = ( case VIS_CHART_TYPES.HeatMap: configOfUser = { ...userSelectedConfigs, - dataConfig: { - ...userSelectedConfigs?.dataConfig, - ...defaultUserConfigs(query, visName), - }, + dataConfig: + userSelectedConfigs?.dataConfig === undefined + ? { ...defaultUserConfigs(query, visName) } + : { + ...userSelectedConfigs?.dataConfig, + }, }; break; case VIS_CHART_TYPES.TreeMap: @@ -189,7 +192,11 @@ const getUserConfigs = ( [GROUPBY]: [ { childField: { ...(axesData.xaxis ? axesData.xaxis[0] : initialEntryTreemap) }, - parentFields: [], + parentFields: + userSelectedConfigs?.dataConfig !== undefined && + userSelectedConfigs.dataConfig[GROUPBY]?.length > 0 + ? [...userSelectedConfigs.dataConfig[GROUPBY][0][PARENTFIELDS]] + : [], }, ], [AGGREGATIONS]: [ diff --git a/public/components/visualizations/charts/lines/line.tsx b/public/components/visualizations/charts/lines/line.tsx index 48ca892e5a..d9b6a79fb6 100644 --- a/public/components/visualizations/charts/lines/line.tsx +++ b/public/components/visualizations/charts/lines/line.tsx @@ -14,7 +14,7 @@ import { PLOTLY_COLOR, VIS_CHART_TYPES, } from '../../../../../common/constants/shared'; -import { hexToRgb, getPropName } from '../../../../components/event_analytics/utils/utils'; +import { getPropName, hexToRgb } from '../../../../components/event_analytics/utils/utils'; import { EmptyPlaceholder } from '../../../event_analytics/explorer/visualizations/shared_components/empty_placeholder'; import { IVisualizationContainerProps } from '../../../../../common/types/explorer'; import { AGGREGATIONS, GROUPBY } from '../../../../../common/constants/explorer'; diff --git a/public/components/visualizations/charts/maps/heatmap.tsx b/public/components/visualizations/charts/maps/heatmap.tsx index a290c12762..c39c70c239 100644 --- a/public/components/visualizations/charts/maps/heatmap.tsx +++ b/public/components/visualizations/charts/maps/heatmap.tsx @@ -2,18 +2,15 @@ * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ - import React, { useMemo } from 'react'; -import { uniq, has, isEmpty, indexOf } from 'lodash'; -import Plotly from 'plotly.js-dist'; import { colorPalette } from '@elastic/eui'; -import { Plt } from '../../plotly/plot'; -import { EmptyPlaceholder } from '../../../event_analytics/explorer/visualizations/shared_components/empty_placeholder'; +import { has, isEmpty, uniq } from 'lodash'; +import Plotly from 'plotly.js-dist'; import { HEATMAP_PALETTE_COLOR, - SINGLE_COLOR_PALETTE, - OPACITY, HEATMAP_SINGLE_COLOR, + OPACITY, + SINGLE_COLOR_PALETTE, } from '../../../../../common/constants/colors'; import { hexToRgb, @@ -21,6 +18,8 @@ import { getPropName, } from '../../../../components/event_analytics/utils/utils'; import { IVisualizationContainerProps } from '../../../../../common/types/explorer'; +import { EmptyPlaceholder } from '../../../event_analytics/explorer/visualizations/shared_components/empty_placeholder'; +import { Plt } from '../../plotly/plot'; import { AGGREGATIONS, GROUPBY } from '../../../../../common/constants/explorer'; export const HeatMap = ({ visualizations, layout, config }: any) => { diff --git a/public/components/visualizations/charts/maps/treemaps.tsx b/public/components/visualizations/charts/maps/treemaps.tsx index e9e7dd5d7d..5761ae5252 100644 --- a/public/components/visualizations/charts/maps/treemaps.tsx +++ b/public/components/visualizations/charts/maps/treemaps.tsx @@ -87,10 +87,10 @@ export const TreeMap = ({ visualizations, layout, config }: any) => { return ; const [treemapData, mergedLayout] = useMemo(() => { - let labelsArray: string[] = [], - parentsArray: string[] = [], - valuesArray: number[] = [], - colorsArray: string[] = []; + let labelsArray: string[] = []; + let parentsArray: string[] = []; + let valuesArray: number[] = []; + let colorsArray: string[] = []; if (parentFields.length === 0) { labelsArray = [...queriedVizData[childField.name]]; diff --git a/public/components/visualizations/charts/pie/pie.tsx b/public/components/visualizations/charts/pie/pie.tsx index ed24022b33..86ea12888f 100644 --- a/public/components/visualizations/charts/pie/pie.tsx +++ b/public/components/visualizations/charts/pie/pie.tsx @@ -104,7 +104,7 @@ export const Pie = ({ visualizations, layout, config }: any) => { labels: labelsOfXAxis, values: queriedVizData[fieldName], type: 'pie', - name: fieldName, + name: getPropName(field), hole: type === 'pie' ? 0 : 0.5, text: fieldName, textinfo: 'percent',