-
Notifications
You must be signed in to change notification settings - Fork 918
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[VisBuilder] Fixes pipeline aggs #3137
Changes from all commits
86d0897
654f937
4b55e30
e65b4cb
55e0f3d
4f9c357
c1e9e1f
133b439
86808b5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,9 +9,9 @@ import React, { FC, useState, useMemo, useEffect, useLayoutEffect } from 'react' | |
import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public'; | ||
import { IExpressionLoaderParams } from '../../../../expressions/public'; | ||
import { VisBuilderServices } from '../../types'; | ||
import { validateSchemaState } from '../utils/validate_schema_state'; | ||
import { validateSchemaState, validateAggregations } from '../utils/validations'; | ||
import { useTypedSelector } from '../utils/state_management'; | ||
import { useVisualizationType } from '../utils/use'; | ||
import { useAggs, useVisualizationType } from '../utils/use'; | ||
import { PersistedState } from '../../../../visualizations/public'; | ||
|
||
import hand_field from '../../assets/hand_field.svg'; | ||
|
@@ -29,6 +29,7 @@ export const Workspace: FC = ({ children }) => { | |
}, | ||
} = useOpenSearchDashboards<VisBuilderServices>(); | ||
const { toExpression, ui } = useVisualizationType(); | ||
const { aggConfigs } = useAggs(); | ||
const [expression, setExpression] = useState<string>(); | ||
const [searchContext, setSearchContext] = useState<IExpressionLoaderParams['searchContext']>({ | ||
query: data.query.queryString.getQuery(), | ||
|
@@ -42,13 +43,17 @@ export const Workspace: FC = ({ children }) => { | |
useEffect(() => { | ||
async function loadExpression() { | ||
const schemas = ui.containerConfig.data.schemas; | ||
const [valid, errorMsg] = validateSchemaState(schemas, rootState.visualization); | ||
|
||
if (!valid) { | ||
if (errorMsg) { | ||
toasts.addWarning(errorMsg); | ||
} | ||
const noAggs = aggConfigs?.aggs?.length === 0; | ||
const schemaValidation = validateSchemaState(schemas, rootState.visualization); | ||
const aggValidation = validateAggregations(aggConfigs?.aggs || []); | ||
|
||
if (noAggs || !aggValidation.valid || !schemaValidation.valid) { | ||
const err = schemaValidation.errorMsg || aggValidation.errorMsg; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would there be cases where you'd want to show both? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thats a UX question but in my opinion, its not really necessary. There are independent errors and the user can only fix one at a time. And since the app is reactive, as soon as they fix one we will display the next error. The same reactive behavior also makes it quite unlikely that the user can cause more than on error at a time. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I like that - a comment about the intentional behavior would be nice for future readers. |
||
|
||
if (err) toasts.addWarning(err); | ||
setExpression(undefined); | ||
|
||
return; | ||
} | ||
|
||
|
@@ -57,7 +62,7 @@ export const Workspace: FC = ({ children }) => { | |
} | ||
|
||
loadExpression(); | ||
}, [rootState, toExpression, toasts, ui.containerConfig.data.schemas, searchContext]); | ||
}, [rootState, toExpression, toasts, ui.containerConfig.data.schemas, searchContext, aggConfigs]); | ||
|
||
useLayoutEffect(() => { | ||
const subscription = data.query.state$.subscribe(({ state }) => { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import { setEditorState } from '../metadata_slice'; | ||
import { RootState, Store } from '../store'; | ||
|
||
export const handlerEditorState = (store: Store, state: RootState, previousState: RootState) => { | ||
const { metadata, ...renderState } = state; | ||
const { metadata: prevMetadata, ...prevRenderState } = previousState; | ||
|
||
// Need to make sure the editorStates are in the clean states(not the initial states) to indicate the viz finished loading | ||
// Because when loading a saved viz from saved object, the previousStore will differ from | ||
// the currentStore even tho there is no changes applied ( aggParams will | ||
// first be empty, and it then will change to not empty once the viz finished loading) | ||
if ( | ||
prevMetadata.editor.state === 'clean' && | ||
metadata.editor.state === 'clean' && | ||
JSON.stringify(renderState) !== JSON.stringify(prevRenderState) | ||
) { | ||
store.dispatch(setEditorState({ state: 'dirty' })); | ||
} | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import { findLast } from 'lodash'; | ||
import { BUCKET_TYPES, IMetricAggType, search } from '../../../../../../data/public'; | ||
import { VisBuilderServices } from '../../../../types'; | ||
import { RootState, Store } from '../store'; | ||
import { setAggParamValue } from '../visualization_slice'; | ||
|
||
/** | ||
* Parent pipeline aggs when combined with histogram aggs need `min_doc_count` to be set appropriately to avoid an error | ||
* on opensearch engine https://opensearch.org/docs/2.4/opensearch/pipeline-agg/#parent-aggregations | ||
*/ | ||
export const handlerParentAggs = async ( | ||
store: Store, | ||
state: RootState, | ||
services: VisBuilderServices | ||
) => { | ||
const { | ||
visualization: { activeVisualization, indexPattern = '' }, | ||
} = state; | ||
|
||
const { | ||
data: { | ||
indexPatterns, | ||
search: { aggs: aggService }, | ||
}, | ||
} = services; | ||
|
||
if (!activeVisualization) return state; | ||
|
||
const aggConfigs = aggService.createAggConfigs( | ||
await indexPatterns.get(indexPattern), | ||
activeVisualization.aggConfigParams | ||
); | ||
|
||
// Pipeline aggs should have a valid bucket agg | ||
const metricAggs = aggConfigs.aggs.filter((agg) => agg.schema === 'metric'); | ||
const lastParentPipelineAgg = findLast( | ||
metricAggs, | ||
({ type }: { type: IMetricAggType }) => type.subtype === search.aggs.parentPipelineType | ||
); | ||
const lastBucket = findLast(aggConfigs.aggs, (agg) => agg.type.type === 'buckets'); | ||
joshuarrrr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
aggConfigs.aggs.forEach((agg) => { | ||
const isLastBucket = lastBucket?.id === agg.id; | ||
// When a Parent Pipeline agg is selected and this agg is the last bucket. | ||
const isLastBucketAgg = isLastBucket && lastParentPipelineAgg && agg.type; | ||
|
||
if ( | ||
isLastBucketAgg && | ||
([BUCKET_TYPES.DATE_HISTOGRAM, BUCKET_TYPES.HISTOGRAM] as any).includes(agg.type.name) | ||
) { | ||
store.dispatch( | ||
setAggParamValue({ | ||
aggId: agg.id, | ||
paramName: 'min_doc_count', | ||
// "histogram" agg has an editor for "min_doc_count" param, which accepts boolean | ||
// "date_histogram" agg doesn't have an editor for "min_doc_count" param, it should be set as a numeric value | ||
value: agg.type.name === 'histogram' ? true : 0, | ||
joshuarrrr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}) | ||
); | ||
} | ||
}); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We may eventually want a more descriptive name than "secondary panel", but 👍 to making the key match the component name.