Skip to content

Commit

Permalink
Merge branch 'feature/129299' of github.com:dej611/kibana into featur…
Browse files Browse the repository at this point in the history
…e/129299
  • Loading branch information
mbondyra committed Sep 7, 2022
2 parents 415f893 + 08deb3f commit f3f2be6
Show file tree
Hide file tree
Showing 8 changed files with 247 additions and 88 deletions.
89 changes: 88 additions & 1 deletion x-pack/plugins/lens/public/visualizations/xy/state_helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@

import { EuiIconType } from '@elastic/eui/src/components/icon/icon';
import type { SavedObjectReference } from '@kbn/core/public';
import type { FramePublicAPI, DatasourcePublicAPI } from '../../types';
import { isQueryAnnotationConfig } from '@kbn/event-annotation-plugin/public';
import { i18n } from '@kbn/i18n';
import { validateQuery } from '../../shared_components';
import type {
FramePublicAPI,
DatasourcePublicAPI,
VisualizationDimensionGroupConfig,
} from '../../types';
import {
visualizationTypes,
XYLayerConfig,
Expand All @@ -17,6 +24,7 @@ import {
YConfig,
XYState,
XYPersistedState,
State,
} from './types';
import { getDataLayers, isAnnotationsLayer, isDataLayer } from './visualization_helpers';

Expand Down Expand Up @@ -151,3 +159,82 @@ export function injectReferences(
}),
};
}

export function validateColumn(
state: State,
frame: Pick<FramePublicAPI, 'dataViews'>,
layerId: string,
columnId: string,
group?: VisualizationDimensionGroupConfig
): { invalid: boolean; invalidMessages?: string[] } {
if (group?.invalid) {
return {
invalid: true,
invalidMessages: group.invalidMessage ? [group.invalidMessage] : undefined,
};
}
const validColumn = { invalid: false };
const layer = state.layers.find((l) => l.layerId === layerId);
if (!layer || !isAnnotationsLayer(layer)) {
return validColumn;
}
const annotation = layer.annotations.find(({ id }) => id === columnId);
if (!annotation || !isQueryAnnotationConfig(annotation)) {
return validColumn;
}
const { dataViews } = frame || {};
const layerDataView = dataViews.indexPatterns[layer.indexPatternId];

const invalidMessages: string[] = [];

if (annotation.timeField && !Boolean(layerDataView.getFieldByName(annotation.timeField))) {
invalidMessages.push(
i18n.translate('xpack.lens.xyChart.annotationError.timeFieldNotFound', {
defaultMessage: 'Time field {timeField} not found in data view {dataView}',
values: { timeField: annotation.timeField, dataView: layerDataView.title },
})
);
}

const { isValid, error } = validateQuery(annotation?.filter, layerDataView);
if (!isValid && error) {
invalidMessages.push(error);
}
if (annotation.textField && !Boolean(layerDataView.getFieldByName(annotation.textField))) {
invalidMessages.push(
i18n.translate('xpack.lens.xyChart.annotationError.textFieldNotFound', {
defaultMessage: 'Text field {textField} not found in data view {dataView}',
values: { textField: annotation.textField, dataView: layerDataView.title },
})
);
}
if (annotation.extraFields?.length) {
const missingTooltipFields = [];
for (const field of annotation.extraFields) {
if (!Boolean(layerDataView.getFieldByName(field))) {
missingTooltipFields.push(field);
}
}
if (missingTooltipFields.length) {
invalidMessages.push(
i18n.translate('xpack.lens.xyChart.annotationError.textFieldNotFound', {
defaultMessage:
'Tooltip {missingFields, plural, one {field} other {fields}} {missingTooltipFields} not found in data view {dataView}',
values: {
missingTooltipFields: missingTooltipFields.join(', '),
missingFields: missingTooltipFields.length,
dataView: layerDataView.title,
},
})
);
}
}

if (!invalidMessages.length) {
return validColumn;
}
return {
invalid: true,
invalidMessages,
};
}
94 changes: 94 additions & 0 deletions x-pack/plugins/lens/public/visualizations/xy/visualization.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import { EventAnnotationConfig } from '@kbn/event-annotation-plugin/common';
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public';
import { DataViewsState } from '../../state_management';
import { createMockedIndexPattern } from '../../indexpattern_datasource/mocks';
import { createMockDataViewsState } from '../../data_views_service/mocks';

const exampleAnnotation: EventAnnotationConfig = {
id: 'an1',
Expand Down Expand Up @@ -2433,6 +2435,98 @@ describe('xy_visualization', () => {
},
]);
});

describe('Annotation layers', () => {
function createStateWithAnnotationProps(annotation: Partial<EventAnnotationConfig>) {
return {
layers: [
{
layerId: 'layerId',
layerType: 'annotations',
indexPatternId: 'first',
annotations: [
{
label: 'Event',
id: '1',
type: 'query',
timeField: 'start_date',
...annotation,
},
],
},
],
} as XYState;
}

function getFrameMock() {
return createMockFramePublicAPI({
datasourceLayers: { first: mockDatasource.publicAPIMock },
dataViews: createMockDataViewsState({
indexPatterns: { first: createMockedIndexPattern() },
}),
});
}
it('should return error if current annotation contains non-existent field as timeField', () => {
const xyState = createStateWithAnnotationProps({
timeField: 'non-existent',
});
const errors = xyVisualization.getErrorMessages(xyState, getFrameMock());
expect(errors).toHaveLength(1);
expect(errors![0]).toEqual(
expect.objectContaining({
shortMessage: 'Time field non-existent not found in data view my-fake-index-pattern',
})
);
});
it('should return error if current annotation contains non existent field as textField', () => {
const xyState = createStateWithAnnotationProps({
textField: 'non-existent',
});
const errors = xyVisualization.getErrorMessages(xyState, getFrameMock());
expect(errors).toHaveLength(1);
expect(errors![0]).toEqual(
expect.objectContaining({
shortMessage: 'Text field non-existent not found in data view my-fake-index-pattern',
})
);
});
it('should contain error if current annotation contains at least one non-existent field as tooltip field', () => {
const xyState = createStateWithAnnotationProps({
extraFields: ['bytes', 'memory', 'non-existent'],
});
const errors = xyVisualization.getErrorMessages(xyState, getFrameMock());
expect(errors).toHaveLength(1);
expect(errors![0]).toEqual(
expect.objectContaining({
shortMessage: 'Tooltip field non-existent not found in data view my-fake-index-pattern',
})
);
});
it('should contain error if current annotation contains invalid query', () => {
const xyState = createStateWithAnnotationProps({
filter: { type: 'kibana_query', query: 'invalid: "', language: 'kuery' },
});
const errors = xyVisualization.getErrorMessages(xyState, getFrameMock());
expect(errors).toHaveLength(1);
expect(errors![0]).toEqual(
expect.objectContaining({
shortMessage: expect.stringContaining(
'Expected "(", "{", value, whitespace but """ found.'
),
})
);
});
it('should contain multiple errors if current annotation contains multiple non-existent fields', () => {
const xyState = createStateWithAnnotationProps({
timeField: 'non-existent',
textField: 'non-existent',
extraFields: ['bytes', 'memory', 'non-existent'],
filter: { type: 'kibana_query', query: 'invalid: "', language: 'kuery' },
});
const errors = xyVisualization.getErrorMessages(xyState, getFrameMock());
expect(errors).toHaveLength(4);
});
});
});

describe('#getWarningMessages', () => {
Expand Down
86 changes: 32 additions & 54 deletions x-pack/plugins/lens/public/visualizations/xy/visualization.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,7 @@ import { i18n } from '@kbn/i18n';
import type { PaletteRegistry } from '@kbn/coloring';
import { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import { CoreStart, ThemeServiceStart } from '@kbn/core/public';
import {
EventAnnotationServiceType,
isQueryAnnotationConfig,
} from '@kbn/event-annotation-plugin/public';
import { EventAnnotationServiceType } from '@kbn/event-annotation-plugin/public';
import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public';
import { FillStyle } from '@kbn/expression-xy-plugin/common';
Expand All @@ -39,7 +36,12 @@ import {
PersistedState,
} from './types';
import { layerTypes } from '../../../common';
import { extractReferences, injectReferences, isHorizontalChart } from './state_helpers';
import {
extractReferences,
injectReferences,
isHorizontalChart,
validateColumn,
} from './state_helpers';
import { toExpression, toPreviewExpression, getSortedAccessors } from './to_expression';
import { getAccessorColorConfigs, getColorAssignments } from './color_assignment';
import { getColumnToLabelMap } from './state_helpers';
Expand Down Expand Up @@ -83,7 +85,6 @@ import { AnnotationsPanel } from './xy_config_panel/annotations_config_panel';
import { DimensionTrigger } from '../../shared_components/dimension_trigger';
import { defaultAnnotationLabel } from './annotations/helpers';
import { onDropForVisualization } from '../../editor_frame_service/editor_frame/config_panel/buttons/drop_targets_utils';
import { validateQuery } from '../../shared_components';

const XY_ID = 'lnsXY';
export const getXyVisualization = ({
Expand Down Expand Up @@ -485,8 +486,10 @@ export const getXyVisualization = ({
return prevState;
}
if (isAnnotationsLayer(foundLayer)) {
const newLayer = { ...foundLayer };
newLayer.annotations = newLayer.annotations.filter(({ id }) => id !== columnId);
const newLayer = {
...foundLayer,
annotations: foundLayer.annotations.filter(({ id }) => id !== columnId),
};

const newLayers = prevState.layers.map((l) => (l.layerId === layerId ? newLayer : l));
return {
Expand Down Expand Up @@ -636,38 +639,11 @@ export const getXyVisualization = ({
),

validateColumn(state, frame, layerId, columnId, group) {
if (group?.invalid) {
return {
invalid: true,
invalidMessage: group.invalidMessage,
};
}
const validColumn = { invalid: false };
const layer = state.layers.find((l) => l.layerId === layerId);
if (!layer || !isAnnotationsLayer(layer)) {
return validColumn;
}
const annotation = layer.annotations.find(({ id }) => id === columnId);
if (!annotation || !isQueryAnnotationConfig(annotation)) {
return validColumn;
const { invalid, invalidMessages } = validateColumn(state, frame, layerId, columnId, group);
if (!invalid) {
return { invalid };
}
const { dataViews } = frame || {};
const layerDataView = dataViews.indexPatterns[layer.indexPatternId];

if (annotation.timeField && !Boolean(layerDataView.getFieldByName(annotation.timeField))) {
return {
invalid: !Boolean(layerDataView.getFieldByName(annotation.timeField)),
invalidMessage: i18n.translate('xpack.lens.xyChart.annotationError.timeFieldNotFound', {
defaultMessage: 'Time field {timeField} not found in data view {dataView}',
values: { timeField: annotation.timeField, dataView: layerDataView.title },
}),
};
}
const { isValid, error } = validateQuery(annotation?.filter, layerDataView);
return {
invalid: !isValid,
invalidMessage: error,
};
return { invalid, invalidMessage: invalidMessages![0] };
},

getErrorMessages(state, frame) {
Expand All @@ -682,26 +658,28 @@ export const getXyVisualization = ({
if (dataViews) {
annotationLayers.forEach((layer) => {
layer.annotations.forEach((annotation) => {
const validatedColumn = this.validateColumn?.(
const validatedColumn = validateColumn(
state,
{ dataViews },
layer.layerId,
annotation.id
);
if (validatedColumn?.invalid && validatedColumn.invalidMessage) {
errors.push({
shortMessage: validatedColumn.invalidMessage,
longMessage: (
<FormattedMessage
id="xpack.lens.xyChart.annotationError"
defaultMessage="Annotation {annotationName} has an error: {errorMessage}"
values={{
annotationName: annotation.label,
errorMessage: validatedColumn.invalidMessage,
}}
/>
),
});
if (validatedColumn?.invalid && validatedColumn.invalidMessages?.length) {
errors.push(
...validatedColumn.invalidMessages.map((invalidMessage) => ({
shortMessage: invalidMessage,
longMessage: (
<FormattedMessage
id="xpack.lens.xyChart.annotationError"
defaultMessage="Annotation {annotationName} has an error: {errorMessage}"
values={{
annotationName: annotation.label,
errorMessage: invalidMessage,
}}
/>
),
}))
);
}
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,14 @@ import {
LensDocShape713,
LensDocShape715,
LensDocShape810,
LensDocShape840,
LensDocShape850,
LensDocShapePre712,
VisState716,
VisState810,
VisState840,
VisState850,
VisStatePre715,
VisStatePre830,
XYVisStatePre840,
XYVisStatePre850,
} from '../migrations/types';
import { extract, inject } from '../../common/embeddable_factory';

Expand Down Expand Up @@ -138,16 +138,16 @@ export const makeLensEmbeddableFactory =
},
'8.5.0': (state) => {
const lensState = state as unknown as {
attributes: LensDocShape840<VisState840>;
attributes: LensDocShape850<VisState850>;
references: SavedObjectReference[] | undefined;
};

let migratedLensState = commonMigrateMetricIds(
lensState.attributes
) as LensDocShape840<XYVisStatePre840>;
) as LensDocShape850<XYVisStatePre850>;
migratedLensState = commonExplicitAnnotationType(migratedLensState);
const migratedReferences = commonAnnotationAddDataViewIdReferences(
migratedLensState as LensDocShape840<VisState840>,
migratedLensState as LensDocShape850<VisState850>,
lensState.references
);
return {
Expand Down
Loading

0 comments on commit f3f2be6

Please sign in to comment.