diff --git a/dashboards-observability/common/constants/explorer.ts b/dashboards-observability/common/constants/explorer.ts index 9d52923fe..564c68167 100644 --- a/dashboards-observability/common/constants/explorer.ts +++ b/dashboards-observability/common/constants/explorer.ts @@ -91,31 +91,31 @@ export const VIZ_CONTAIN_XY_AXIS = [ // default ppl aggregation method options export const AGGREGATION_OPTIONS = [ { - label: 'COUNT', + label: 'count', }, { - label: 'SUM', + label: 'sum', }, { - label: 'AVERAGE', + label: 'avg', }, { - label: 'MAX', + label: 'max', }, { - label: 'MIN', + label: 'min', }, { - label: 'VAR_SAMP', + label: 'var_samp', }, { - label: 'VAR_POP', + label: 'var_pop', }, { - label: 'STDDEV_SAMP', + label: 'stddev_samp', }, { - label: 'STDDEV_POP', + label: 'stddev_pop', }, ]; diff --git a/dashboards-observability/common/query_manager/ast/expression/span.ts b/dashboards-observability/common/query_manager/ast/expression/span.ts index d69152b44..d2a570915 100644 --- a/dashboards-observability/common/query_manager/ast/expression/span.ts +++ b/dashboards-observability/common/query_manager/ast/expression/span.ts @@ -23,6 +23,6 @@ export class Span extends PPLNode { } toString(): string { - return `${this.spanExpression.toString()} as ${this.alias}`; + return `${this.spanExpression.toString()}${this.alias ? ` as ${this.alias}` : ''}`; } } diff --git a/dashboards-observability/common/types/explorer.ts b/dashboards-observability/common/types/explorer.ts index 9d3cb5072..a734d7150 100644 --- a/dashboards-observability/common/types/explorer.ts +++ b/dashboards-observability/common/types/explorer.ts @@ -265,12 +265,21 @@ export interface ConfigListEntry { side: string; type: string; } + export interface HistogramConfigList { bucketSize: string; bucketOffset: string; } +export interface DimensionSpan { + time_field: IField; + interval: number; + unit: string; +} + export interface ConfigList { dimensions?: ConfigListEntry[] | HistogramConfigList[]; metrics?: ConfigListEntry[]; + breakdowns?: ConfigListEntry[] | HistogramConfigList[]; + span?: DimensionSpan; } diff --git a/dashboards-observability/public/.DS_Store b/dashboards-observability/public/.DS_Store new file mode 100644 index 000000000..dbd07c1a1 Binary files /dev/null and b/dashboards-observability/public/.DS_Store differ diff --git a/dashboards-observability/public/components/common/search/autocomplete.tsx b/dashboards-observability/public/components/common/search/autocomplete.tsx index 3d3fdac26..a8c4007a7 100644 --- a/dashboards-observability/public/components/common/search/autocomplete.tsx +++ b/dashboards-observability/public/components/common/search/autocomplete.tsx @@ -185,7 +185,13 @@ export const Autocomplete = (props: AutocompleteProps) => {
-
+ + {fullWord.slice(0, -item.suggestion.length)} + {item.suggestion} + +
+ {/*
{ }
`, }} - /> + /> */}
diff --git a/dashboards-observability/public/components/common/search/search.tsx b/dashboards-observability/public/components/common/search/search.tsx index b4e2ebb16..ed9f85381 100644 --- a/dashboards-observability/public/components/common/search/search.tsx +++ b/dashboards-observability/public/components/common/search/search.tsx @@ -16,8 +16,8 @@ import { EuiBadge, EuiContextMenuPanel, EuiToolTip, + EuiCallOut } from '@elastic/eui'; -import _ from 'lodash'; import { DatePicker } from './date_picker'; import '@algolia/autocomplete-theme-classic'; import { Autocomplete } from './autocomplete'; @@ -82,10 +82,10 @@ export const Search = (props: any) => { stopLive, setIsLiveTailPopoverOpen, liveTailName, + searchError = null, } = props; - + const appLogEvents = tabId.match(APP_ANALYTICS_TAB_ID_REGEX); - const [isSavePanelOpen, setIsSavePanelOpen] = useState(false); const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); @@ -247,6 +247,17 @@ export const Search = (props: any) => { )} + { searchError && searchError.error && ( + + + +

+ {JSON.parse(searchError.message).error.details} +

+
+
+
) + } {flyout} ); diff --git a/dashboards-observability/public/components/custom_panels/helpers/__tests__/__snapshots__/utils.test.tsx.snap b/dashboards-observability/public/components/custom_panels/helpers/__tests__/__snapshots__/utils.test.tsx.snap index 24c8f5e45..e31ae697b 100644 --- a/dashboards-observability/public/components/custom_panels/helpers/__tests__/__snapshots__/utils.test.tsx.snap +++ b/dashboards-observability/public/components/custom_panels/helpers/__tests__/__snapshots__/utils.test.tsx.snap @@ -1477,188 +1477,80 @@ exports[`Utils helper functions renders displayVisualization function 1`] = ` } } > - - +
- - + className="euiText euiText--extraSmall lnsChart__empty" + data-test-subj="vizWorkspace__noData" + > + +
+ +
+ + + + + + +
+ +

+ + + No results found + + +

+
+ +
+ +
+ + @@ -4307,188 +4199,80 @@ exports[`Utils helper functions renders displayVisualization function 3`] = ` } } > - - +
- - + className="euiText euiText--extraSmall lnsChart__empty" + data-test-subj="vizWorkspace__noData" + > + +
+ +
+ + + + + + +
+ +

+ + + No results found + + +

+
+ +
+ +
+ + @@ -5225,132 +5009,80 @@ exports[`Utils helper functions renders displayVisualization function 4`] = ` } } > - - +
- - + className="euiText euiText--extraSmall lnsChart__empty" + data-test-subj="vizWorkspace__noData" + > + +
+ +
+ + + + + + +
+ +

+ + + No results found + + +

+
+ +
+ +
+ + diff --git a/dashboards-observability/public/components/event_analytics/explorer/explorer.tsx b/dashboards-observability/public/components/event_analytics/explorer/explorer.tsx index b375987bf..b1c2751f5 100644 --- a/dashboards-observability/public/components/event_analytics/explorer/explorer.tsx +++ b/dashboards-observability/public/components/event_analytics/explorer/explorer.tsx @@ -66,6 +66,7 @@ import { selectFields, updateFields, sortFields } from '../redux/slices/field_sl import { updateTabName } from '../redux/slices/query_tab_slice'; import { selectCountDistribution } from '../redux/slices/count_distribution_slice'; import { selectExplorerVisualization } from '../redux/slices/visualization_slice'; +import { change as changeVizConfig } from '../redux/slices/viualization_config_slice'; import { selectVisualizationConfig, change as changeVisualizationConfig, @@ -490,6 +491,7 @@ export const Explorer = ({ handleQuerySearch(availability); }; + /** * Toggle fields between selected and unselected sets * @param field field to be toggled @@ -821,51 +823,6 @@ export const Explorer = ({ ); }; - const testAntlr = (query: string) => { - const qm = new QueryManager(); - // const pplQueryBuilder = new PPLQueryBuilder(); - - // build query - const res = - qm - .queryBuilder() - .addSource('index') - .addPipe() - .addStats() - .addMetrics([ - { - field: '', - agg_func: 'count' - }, - { - field: 'bytes', - agg_func: 'avg', - alias: 'avg_bytes' - } - ]) - .addBy() - .addGroupBy([{ - field: 'host' - }, - { - field: 'tags' - } - ]) - .getQuery(); - - console.log('built res: ', res); - - // parse query - const parsed_res = - qm - .queryParser() - .parse(query) - .getStats(); - - console.log('parsed res: ', parsed_res); - - }; - const handleQuerySearch = useCallback( async (availability?: boolean) => { @@ -881,10 +838,39 @@ export const Explorer = ({ if (availability !== true) { await updateQueryInStore(tempQuery); } - testAntlr(query[RAW_QUERY]); - fetchData(); + await fetchData(); + + if (selectedContentTabId === TAB_CHART_ID) { + // parse stats section on every search + const qm = new QueryManager(); + const statsTokens = + qm + .queryParser() + .parse(tempQuery) + .getStats(); + + await dispatch( + changeVizConfig({ + tabId, + vizId: curVisId, + data: { + dataConfig: { + metrics: statsTokens.aggregations.map((agg) => ({ + 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 ?? '', + })), + }, + }, + }) + ); + } }, - [tempQuery, query[RAW_QUERY]] + [tempQuery, query, selectedContentTabId] ); const handleQueryChange = async (newQuery: string) => setTempQuery(newQuery); @@ -1197,6 +1183,10 @@ export const Explorer = ({ explorerVisualizations, setToast, pplService, + handleQuerySearch, + handleQueryChange, + setTempQuery, + fetchData, explorerFields, explorerData, http, @@ -1236,6 +1226,7 @@ export const Explorer = ({ stopLive={stopLive} setIsLiveTailPopoverOpen={setIsLiveTailPopoverOpen} liveTailName={liveTailNameRef.current} + searchError={explorerVisualizations} /> ({ + name, + label: name, + type, +}); export const DataConfigPanelItem = ({ fieldOptionList, visualizations }: any) => { const dispatch = useDispatch(); - const { tabId, curVisId, changeVisualizationConfig } = useContext(TabContext); + const { tabId, handleQuerySearch, handleQueryChange, setTempQuery, fetchData } = useContext( + TabContext + ); + const explorerVisualizationConfigs = useSelector(selectVisualizationConfig)[tabId]; const { data } = visualizations; - const { userConfigs } = data; - const { data: vizData = {}, metadata: { fields = [] } = {} } = data?.rawVizData; - - const initialConfigEntry = { - label: '', - aggregation: '', - custom_label: '', - name: '', - side: 'right', - type: '', - }; - + const { + indexFields: { availableFields }, + } = data; const [configList, setConfigList] = useState({}); useEffect(() => { - if (userConfigs && userConfigs.dataConfig && userConfigs.dataConfig.valueOptions) { + if ( + data.rawVizData?.[visualizations.vis.name] && + data.rawVizData?.[visualizations.vis.name].dataConfig + ) { + setConfigList((staleState) => { + return { + ...staleState, + ...data.rawVizData[visualizations.vis.name].dataConfig, + }; + }); + } else if (some(SPECIAL_RENDERING_VIZS, (visType) => visType === visualizations.vis.name)) { + // any vis that doesn't conform normal metrics/dimensions data confiurations setConfigList({ - ...userConfigs.dataConfig.valueOptions, + ...DEFAULT_DATA_CONFIGS[visualizations.vis.name], }); + } else { + // default + const qm = new QueryManager(); + const statsTokens = qm.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?.valueOptions, visualizations.vis.name]); + }, [ + data.defaultAxes, + data.rawVizData?.[visualizations.vis.name]?.dataConfig, + visualizations.vis.name, + ]); const updateList = (value: string, index: number, name: string, field: string) => { - let list = { ...configList }; + const list = { ...configList }; let listItem = { ...list[name][index] }; listItem = { ...listItem, @@ -79,7 +162,7 @@ export const DataConfigPanelItem = ({ fieldOptionList, visualizations }: any) => const updateHistogramConfig = (configName: string, fieldName: string, value: string) => { const list = { ...configList }; - let listItem = { ...list[configName][0] }; + const listItem = { ...list[configName][0] }; listItem[fieldName] = value; const updatedList = { ...list, @@ -101,35 +184,47 @@ export const DataConfigPanelItem = ({ fieldOptionList, visualizations }: any) => setConfigList(updatedList); }; - const updateChart = () => { - dispatch( - changeVisualizationConfig({ - tabId, - vizId: curVisId, - data: { - ...userConfigs, - dataConfig: { - ...userConfigs.dataConfig, - valueOptions: { - dimensions: configList.dimensions, - metrics: configList.metrics, + const updateChart = (updatedConfigList = configList) => { + const qm = new QueryManager(); + const statsTokens = qm.queryParser().parse(data.query.rawQuery).getStats(); + const newQuery = qm + .queryBuilder() + .build(data.query.rawQuery, composeAggregations(updatedConfigList, statsTokens)); + + batch(async () => { + await handleQueryChange(newQuery); + await dispatch( + changeQuery({ + tabId, + query: { + ...data.query, + [RAW_QUERY]: newQuery, + }, + }) + ); + await fetchData(); + await dispatch( + changeVizConfig({ + tabId, + vizId: visualizations.vis.name, + data: { + dataConfig: { + metrics: updatedConfigList.metrics, + dimensions: updatedConfigList.dimensions, + breakdowns: updatedConfigList.breakdowns, + span: updatedConfigList.span, }, }, - }, - }) - ); + }) + ); + }); }; const isPositionButtonVisible = (sectionName: string) => - sectionName === 'metrics' && - (visualizations.vis.name === visChartTypes.Line || - visualizations.vis.name === visChartTypes.Scatter); + sectionName === 'metrics' && visualizations.vis.name === visChartTypes.Line; const getOptionsAvailable = (sectionName: string) => { - let selectedFields = {}; - for (const key in configList) { - configList[key] && configList[key].forEach((field) => (selectedFields[field.label] = true)); - } + const selectedFields = {}; const unselectedFields = fieldOptionList.filter((field) => !selectedFields[field.label]); return sectionName === 'metrics' ? unselectedFields @@ -138,106 +233,133 @@ export const DataConfigPanelItem = ({ fieldOptionList, visualizations }: any) => : unselectedFields; }; - const getCommonUI = (lists, sectionName: string) => - lists && - lists.map((singleField, index: number) => ( - -
-
- {sectionName === 'dimensions' && visualizations.vis.name === visChartTypes.HeatMap && ( - -
{index === 0 ? 'X-Axis' : 'Y-Axis'}
-
- )} - - {sectionName == 'metrics' && ( - - handleServiceRemove(index, sectionName)} - /> - - ) - } - > - - updateList(e.length > 0 ? e[0].label : '', index, sectionName, 'aggregation') - } - /> - - )} - - - 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')} - /> - - )} + const getCommonUI = (lists, sectionName: string) => { + return ( + <> + {Array.isArray(lists) && + lists.map((singleField, index: number) => ( + <> +
+
+ {sectionName === 'dimensions' && + visualizations.vis.name === visChartTypes.HeatMap && ( + +
{index === 0 ? 'X-Axis' : 'Y-Axis'}
+
+ )} + + {sectionName === 'metrics' && ( + + 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 !== visChartTypes.HeatMap && lists.length - 1 === index && ( - - handleServiceAdd(sectionName)} - disabled={ - sectionName === 'dimensions' && visualizations.vis.name === visChartTypes.Line - } - > - Add - - - )} -
-
-
- -
- )); + + ))} + {visualizations.vis.name !== visChartTypes.HeatMap && ( + + handleServiceAdd(sectionName)} + disabled={ + sectionName === 'dimensions' && visualizations.vis.name === visChartTypes.Line + } + > + Add + + + )} + + ); + }; const getNumberField = (type: string) => ( <> @@ -259,6 +381,124 @@ export const DataConfigPanelItem = ({ fieldOptionList, visualizations }: any) => ); + const getBreakDownFields = useCallback( + (configList) => { + return configList.dimensions; + }, + [configList.dimensions] + ); + + const Breakdowns = useMemo(() => { + return ( + <> +
+
+ + + { + setConfigList((staleState) => { + return { + ...staleState, + breakdowns: fields, + }; + }); + }} + /> + + +
+
+ + ); + }, [configList.dimensions, configList.breakdowns]); + + const DateHistogram = useMemo(() => { + return ( + <> +
+
+ + + idxField.type === 'timestamp') + .map((field) => ({ ...field, label: field.name }))} + selectedOptions={ + configList.span?.time_field ? [...configList.span?.time_field] : [] + } + onChange={(field) => { + setConfigList((staleState) => { + return { + ...staleState, + span: { + ...staleState.span, + time_field: field, + }, + }; + }); + }} + /> + + + { + setConfigList((staleState) => { + return { + ...staleState, + span: { + ...staleState.span, + interval: e.target.value, + }, + }; + }); + }} + aria-label="Use aria labels when no actual label is in use" + /> + + + { + return { + ...option, + label: option.text, + }; + })} + selectedOptions={configList.span?.unit ? [...configList.span?.unit] : []} + onChange={(unit) => { + setConfigList((staleState) => { + return { + ...staleState, + span: { + ...staleState.span, + unit, + }, + }; + }); + }} + /> + + +
+
+ + ); + }, [availableFields, configList.span]); + return ( <> @@ -267,16 +507,26 @@ export const DataConfigPanelItem = ({ fieldOptionList, visualizations }: any) => {visualizations.vis.name !== visChartTypes.Histogram ? ( <> + +

Series

+
+ + {getCommonUI(configList.metrics, 'metrics')} +

Dimensions

+ {getCommonUI(configList.dimensions, 'dimensions')} - -

Metrics

+

Date Histogram

- {getCommonUI(configList.metrics, 'metrics')} + {DateHistogram} + {/* +

Breakdowns

+
+ {Breakdowns} */} ) : ( <> @@ -292,11 +542,12 @@ export const DataConfigPanelItem = ({ fieldOptionList, visualizations }: any) => {getNumberField('bucketOffset')} )} + updateChart()} size="s" > Update chart diff --git a/dashboards-observability/public/components/event_analytics/explorer/visualizations/index.tsx b/dashboards-observability/public/components/event_analytics/explorer/visualizations/index.tsx index 2aa7c062d..2e51c1a7e 100644 --- a/dashboards-observability/public/components/event_analytics/explorer/visualizations/index.tsx +++ b/dashboards-observability/public/components/event_analytics/explorer/visualizations/index.tsx @@ -52,7 +52,8 @@ export const ExplorerVisualizations = ({ const { data, vis } = visualizations; const { data: vizData = {}, metadata: { fields = [] } = {} } = data?.rawVizData; - const fieldOptionList = fields.map((field) => { + const fieldOptionList = explorerFields.availableFields.map((field) => { + // const fieldOptionList = fields.map((field) => { return { ...field, label: field.name }; }); diff --git a/dashboards-observability/public/components/event_analytics/hooks/use_fetch_visualizations.ts b/dashboards-observability/public/components/event_analytics/hooks/use_fetch_visualizations.ts index 866921201..446bcf229 100644 --- a/dashboards-observability/public/components/event_analytics/hooks/use_fetch_visualizations.ts +++ b/dashboards-observability/public/components/event_analytics/hooks/use_fetch_visualizations.ts @@ -37,7 +37,8 @@ export const useFetchVisualizations = ({ const fetchVisualizations = async ( { query }: { query: string }, format: string, - handler: (res: any) => void + successHandler: (res: any) => void, + errorHandler: (error: any) => void ) => { setIsVisLoading(true); @@ -45,16 +46,16 @@ export const useFetchVisualizations = ({ .fetch({ query, format, + }, (error) => { + errorHandler(error); + setIsVisLoading(false); }) .then((res: any) => { - handler(res); - }) - .catch((err: any) => { - console.error(err); - }) - .finally(() => { + if (res && res.status === 200) { + successHandler(res); + } setIsVisLoading(false); - }); + }) }; const getCountVisualizations = (interval: string) => { @@ -74,7 +75,8 @@ export const useFetchVisualizations = ({ data: res, }) ); - } + }, + (error: Error) => {} ); }; @@ -118,6 +120,14 @@ export const useFetchVisualizations = ({ }) ); }); + }, + (error: any) => { + dispatch( + renderExplorerVis({ + tabId: requestParams.tabId, + data: error.body, + }) + ); } ); }; diff --git a/dashboards-observability/public/components/visualizations/charts/__tests__/__snapshots__/bar.test.tsx.snap b/dashboards-observability/public/components/visualizations/charts/__tests__/__snapshots__/bar.test.tsx.snap index 34b400f3a..9a88fe43b 100644 --- a/dashboards-observability/public/components/visualizations/charts/__tests__/__snapshots__/bar.test.tsx.snap +++ b/dashboards-observability/public/components/visualizations/charts/__tests__/__snapshots__/bar.test.tsx.snap @@ -533,9 +533,7 @@ exports[`Bar component Renders bar component 1`] = ` } } > - + { const DEFAULT_LABEL_SIZE = 10; - const { vis } = visualizations; const { - data, - metadata: { fields }, - } = visualizations.data.rawVizData; + data: { + defaultAxes, + indexFields, + query, + rawVizData: { + data: queriedVizData, + metadata: { fields }, + }, + userConfigs, + }, + vis: visMetaData, + }: IVisualizationContainerProps = visualizations; const lastIndex = fields.length - 1; - const { - dataConfig = {}, - layoutConfig = {}, - availabilityConfig = {}, - } = visualizations?.data?.userConfigs; - const dataConfigTab = - visualizations.data?.rawVizData?.bar?.dataConfig && - visualizations.data.rawVizData.bar.dataConfig; - const xaxis = dataConfig?.valueOptions?.dimensions - ? dataConfig.valueOptions.dimensions.filter((item) => item.label) - : []; - const yaxis = dataConfig?.valueOptions?.metrics - ? dataConfig.valueOptions.metrics.filter((item) => item.label) - : []; - const barOrientation = dataConfig?.chartStyles?.orientation || vis.orientation; - const isVertical = barOrientation === vis.orientation; - const tooltipMode = - dataConfig?.tooltipOptions?.tooltipMode !== undefined - ? dataConfig.tooltipOptions.tooltipMode - : 'show'; - const tooltipText = - dataConfig?.tooltipOptions?.tooltipText !== undefined - ? dataConfig.tooltipOptions.tooltipText - : 'all'; - let bars, valueSeries, valueForXSeries; - - if (!isEmpty(xaxis) && !isEmpty(yaxis)) { - valueSeries = isVertical ? [...yaxis] : [...xaxis]; - valueForXSeries = isVertical ? [...xaxis] : [...yaxis]; - } else { - return ; - } - - const tickAngle = dataConfig?.chartStyles?.rotateBarLabels || vis.labelangle; - const lineWidth = dataConfig?.chartStyles?.lineWidth || vis.linewidth; + const { dataConfig = {}, layoutConfig = {}, availabilityConfig = {} } = userConfigs; + console.log('bar dataConfig: ', dataConfig); + + if ( + isEmpty(queriedVizData) || + !Array.isArray(dataConfig.dimensions) || + !Array.isArray(dataConfig.metrics) || + (dataConfig.breakdowns && !Array.isArray(dataConfig.breakdowns)) + ) + return ; + + /** + * determine stylings + */ + const barOrientation = dataConfig.chartStyles?.orientation || visMetaData.orientation; + const isVertical = barOrientation === visMetaData.orientation; + + const tickAngle = dataConfig?.chartStyles?.rotateBarLabels || visMetaData.labelangle; + const lineWidth = dataConfig?.chartStyles?.lineWidth || visMetaData.linewidth; const fillOpacity = dataConfig?.chartStyles?.fillOpacity !== undefined ? dataConfig?.chartStyles?.fillOpacity / FILLOPACITY_DIV_FACTOR - : vis.fillOpacity / FILLOPACITY_DIV_FACTOR; - const barWidth = 1 - (dataConfig?.chartStyles?.barWidth || vis.barwidth); - const groupWidth = 1 - (dataConfig?.chartStyles?.groupWidth || vis.groupwidth); + : visMetaData.fillOpacity / FILLOPACITY_DIV_FACTOR; + const barWidth = 1 - (dataConfig?.chartStyles?.barWidth || visMetaData.barwidth); + const groupWidth = 1 - (dataConfig?.chartStyles?.groupWidth || visMetaData.groupwidth); const showLegend = !( - dataConfig?.legend?.showLegend && dataConfig.legend.showLegend !== vis.showlegend + dataConfig?.legend?.showLegend && dataConfig.legend.showLegend !== visMetaData.showlegend ); - const legendPosition = dataConfig?.legend?.position || vis.legendposition; + const legendPosition = dataConfig?.legend?.position || visMetaData.legendposition; visualizations.data?.rawVizData?.dataConfig?.metrics ? visualizations.data?.rawVizData?.dataConfig?.metrics : []; @@ -77,103 +70,77 @@ export const Bar = ({ visualizations, layout, config }: any) => { ?.color) || PLOTLY_COLOR[index % PLOTLY_COLOR.length]; - const prepareData = (valueForXSeries) => { - return valueForXSeries - .map((dimension: any) => data[dimension.label]) - ?.reduce((prev, cur) => { - return prev.map((i, j) => `${i}, ${cur[j]}`); - }); - }; + let bars, valueSeries, valueForXSeries; - const createNameData = (nameData, metricName: string) => - nameData?.map((el) => el + ',' + metricName); - - // for multiple dimention and metrics with timestamp - if (valueForXSeries.some((e) => e.type === 'timestamp')) { - const nameData = - valueForXSeries.length > 1 - ? valueForXSeries - .filter((item) => item.type !== 'timestamp') - .map((dimension) => data[dimension.label]) - .reduce((prev, cur) => { - return prev.map((i, j) => `${i}, ${cur[j]}`); - }) - : []; - - let dimensionsData = valueForXSeries - .filter((item) => item.type === 'timestamp') - .map((dimension) => data[dimension.label]) - .flat(); - - bars = valueSeries - .map((field: any, index: number) => { - const selectedColor = getSelectedColorTheme(field, index); - return dimensionsData.map((dimension: any, j: number) => { - return { - x: isVertical - ? !isEmpty(xaxis) - ? dimension - : data[fields[lastIndex].name] - : data[field.label], - y: isVertical ? data[field.label][j] : dimensionsData, // TODO: orinetation - type: vis.type, - marker: { - color: hexToRgb(selectedColor, fillOpacity), - line: { - color: selectedColor, - width: lineWidth, - }, - }, - name: nameData.length > 0 ? createNameData(nameData, field.label)[j] : field.label, // dimensionsData[index]+ ',' + field.label, - hoverinfo: tooltipMode === 'hidden' ? 'none' : tooltipText, - orientation: barOrientation, - }; - }); - }) - .flat(); - - // merging x, y for same names - bars = Object.values( - bars?.reduce((acc, { x, y, name, type, marker, orientation, hoverinfo }) => { - acc[name] = acc[name] || { x: [], y: [], name, type, marker, orientation, hoverinfo }; - acc[name].x.push(x); - acc[name].y.push(y); - - return acc; - }, {}) - ); - } else { - // for multiple dimention and metrics without timestamp - const dimensionsData = prepareData(valueForXSeries); - const metricsData = prepareData(valueSeries); - bars = valueSeries.map((field: any, index: number) => { - const selectedColor = getSelectedColorTheme(field, index); - return { - x: isVertical - ? !isEmpty(xaxis) - ? dimensionsData - : data[fields[lastIndex].name] - : data[field.name], - y: isVertical ? data[field.name] : metricsData, // TODO: add if isempty true - type: vis.type, - marker: { - color: hexToRgb(selectedColor, fillOpacity), - line: { - color: selectedColor, - width: lineWidth, - }, + /** + * determine x axis + */ + const xaxes = useMemo(() => { + // breakdown selections + if (dataConfig.breakdowns) { + return [ + ...dataConfig.dimensions.filter( + (dimension) => + !some(dataConfig.breakdowns, (breakdown) => breakdown.label === dimension.label) + ), + ]; + } + + // span selection + const timestampField = find(fields, (field) => field.type === 'timestamp'); + if (dataConfig.span && dataConfig.span.time_field && timestampField) { + return [timestampField, ...dataConfig.dimensions]; + } + + return [...dataConfig.dimensions]; + }, [dataConfig.dimensions, dataConfig.breakdowns]); + + /** + * determine y axis + */ + const yaxes = useMemo(() => { + return Array.isArray(dataConfig.metrics) ? [...dataConfig.metrics] : []; + }, [dataConfig.metrics]); + + /** + * prepare data for visualization, map x-xais to y-xais + */ + const chartAxis = useMemo(() => { + return Array.isArray(queriedVizData[`${yaxes[0].aggregation}(${yaxes[0].name})`]) + ? queriedVizData[`${yaxes[0].aggregation}(${yaxes[0].name})`].map((_, idx) => { + // let combineXaxis = ''; + const xaxisName = xaxes.map((xaxis) => { + return queriedVizData[xaxis.name] && queriedVizData[xaxis.name][idx] + ? queriedVizData[xaxis.name][idx] + : ''; + }); + return xaxisName.join(', '); + }) + : []; + }, [queriedVizData, xaxes, yaxes]); + + bars = yaxes?.map((yMetric, idx) => { + return { + y: isVertical ? queriedVizData[`${yMetric.aggregation}(${yMetric.name})`] : chartAxis, + x: isVertical ? chartAxis : queriedVizData[`${yMetric.aggregation}(${yMetric.name})`], + type: visMetaData.type, + marker: { + color: getSelectedColorTheme(yMetric, idx), + line: { + color: getSelectedColorTheme(yMetric, idx), + width: lineWidth, }, - name: field.name, - hoverinfo: tooltipMode === 'hidden' ? 'none' : tooltipText, - orientation: barOrientation, - }; - }); - } + }, + name: yMetric.name, + orientation: barOrientation, + }; + }); // If chart has length of result buckets < 16 // then use the LONG_CHART_COLOR for all the bars in the chart const plotlyColorway = - data[fields[lastIndex].name].length < 16 ? PLOTLY_COLOR : [LONG_CHART_COLOR]; + queriedVizData[fields[lastIndex].name].length < 16 ? PLOTLY_COLOR : [LONG_CHART_COLOR]; + const mergedLayout = { colorway: plotlyColorway, ...layout, @@ -208,15 +175,17 @@ export const Bar = ({ visualizations, layout, config }: any) => { const mapToLine = (list: ThresholdUnitType[] | AvailabilityUnitType[], lineStyle: any) => { return list.map((thr: ThresholdUnitType) => { thresholdTraces.x.push( - data[!isEmpty(xaxis) ? xaxis[xaxis.length - 1]?.label : fields[lastIndex].name][0] + queriedVizData[ + !isEmpty(xaxis) ? xaxis[xaxis.length - 1]?.label : fields[lastIndex].name + ][0] ); thresholdTraces.y.push(thr.value * (1 + 0.06)); thresholdTraces.text.push(thr.name); return { type: 'line', - x0: data[!isEmpty(xaxis) ? xaxis[0]?.label : fields[lastIndex].name][0], + x0: queriedVizData[!isEmpty(xaxis) ? xaxis[0]?.label : fields[lastIndex].name][0], y0: thr.value, - x1: last(data[!isEmpty(xaxis) ? xaxis[0]?.label : fields[lastIndex].name]), + x1: last(queriedVizData[!isEmpty(xaxis) ? xaxis[0]?.label : fields[lastIndex].name]), y1: thr.value, name: thr.name || '', opacity: 0.7, @@ -232,13 +201,11 @@ export const Bar = ({ visualizations, layout, config }: any) => { mergedLayout.shapes = [...mapToLine(thresholds, { dash: 'dashdot' }), ...mapToLine(levels, {})]; bars = [...bars, thresholdTraces]; } - const mergedConfigs = useMemo( - () => ({ - ...config, - ...(layoutConfig.config && layoutConfig.config), - }), - [config, layoutConfig.config] - ); + + const mergedConfigs = { + ...config, + ...(layoutConfig.config && layoutConfig.config), + }; return ; }; diff --git a/dashboards-observability/public/services/requests/ppl.ts b/dashboards-observability/public/services/requests/ppl.ts index f9343bc93..4fe252f22 100644 --- a/dashboards-observability/public/services/requests/ppl.ts +++ b/dashboards-observability/public/services/requests/ppl.ts @@ -24,7 +24,7 @@ export default class PPLService { body: JSON.stringify(params), }) .catch((error) => { - console.error(error); + console.error('fetch error: ', error.body); if (errorHandler) errorHandler(error); }); };