[9.0] [Lens] Do not crash when editing a Lens chart with a by reference annotation layer (#213090) (#215654)

# Backport

This will backport the following commits from `main` to `9.0`:
- [[Lens] Do not crash when editing a Lens chart with a by reference
annotation layer
(#213090)](https://github.com/elastic/kibana/pull/213090)

<!--- Backport version: 9.6.6 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sorenlouv/backport)

<!--BACKPORT [{"author":{"name":"Marco
Liberati","email":"dej611@users.noreply.github.com"},"sourceCommit":{"committedDate":"2025-03-06T11:44:30Z","message":"[Lens]
Do not crash when editing a Lens chart with a by reference annotation
layer (#213090)\n\n## Summary\n\nFixes #212917\n\nThe root problem is
belongs into the annotation layer logic to produce\nthe reference id for
the persisted saved object.\nIn the previous logic a new `uuid` was
generated all the time leading to\na continuous flow of `setState` calls
to update the \"runtime\" state of\nthe Lens object when inline editing:
the fix was to produce a stable id\nin the `extractReferences` logic to
avoid the re-renders.\nThe logic has been tweaked a bit now with some
extra explanations inline\nto make it more understandable.\n\nNew tests
have been added to smoke test this scenario.\n\n### Checklist\n\nCheck
the PR satisfies following conditions. \n\n- [x] [Unit or
functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere
updated or added to match the most common
scenarios\n\n---------\n\nCo-authored-by: Nick Partridge
<nick.ryan.partridge@gmail.com>","sha":"48926e5173ebec2444a3ec6f244bbadb42eab3b0","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix","Team:Visualizations","Feature:Lens","backport:version","v8.18.0","v9.1.0"],"title":"[Lens]
Do not crash when editing a Lens chart with a by reference annotation
layer","number":213090,"url":"https://github.com/elastic/kibana/pull/213090","mergeCommit":{"message":"[Lens]
Do not crash when editing a Lens chart with a by reference annotation
layer (#213090)\n\n## Summary\n\nFixes #212917\n\nThe root problem is
belongs into the annotation layer logic to produce\nthe reference id for
the persisted saved object.\nIn the previous logic a new `uuid` was
generated all the time leading to\na continuous flow of `setState` calls
to update the \"runtime\" state of\nthe Lens object when inline editing:
the fix was to produce a stable id\nin the `extractReferences` logic to
avoid the re-renders.\nThe logic has been tweaked a bit now with some
extra explanations inline\nto make it more understandable.\n\nNew tests
have been added to smoke test this scenario.\n\n### Checklist\n\nCheck
the PR satisfies following conditions. \n\n- [x] [Unit or
functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere
updated or added to match the most common
scenarios\n\n---------\n\nCo-authored-by: Nick Partridge
<nick.ryan.partridge@gmail.com>","sha":"48926e5173ebec2444a3ec6f244bbadb42eab3b0"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"8.18","label":"v8.18.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"url":"https://github.com/elastic/kibana/pull/213366","number":213366,"state":"MERGED","mergeCommit":{"sha":"fc72350d24acfcb9018176752fa931fe6018072d","message":"[8.18]
[Lens] Do not crash when editing a Lens chart with a by reference
annotation layer (#213090) (#213366)\n\n# Backport\n\nThis will backport
the following commits from `main` to `8.18`:\n- [[Lens] Do not crash
when editing a Lens chart with a by reference\nannotation
layer\n(#213090)](https://github.com/elastic/kibana/pull/213090)\n\n\n\n###
Questions ?\nPlease refer to the [Backport
tool\ndocumentation](https://github.com/sorenlouv/backport)\n\n\n\nCo-authored-by:
Marco Liberati
<dej611@users.noreply.github.com>"}},{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/213090","number":213090,"mergeCommit":{"message":"[Lens]
Do not crash when editing a Lens chart with a by reference annotation
layer (#213090)\n\n## Summary\n\nFixes #212917\n\nThe root problem is
belongs into the annotation layer logic to produce\nthe reference id for
the persisted saved object.\nIn the previous logic a new `uuid` was
generated all the time leading to\na continuous flow of `setState` calls
to update the \"runtime\" state of\nthe Lens object when inline editing:
the fix was to produce a stable id\nin the `extractReferences` logic to
avoid the re-renders.\nThe logic has been tweaked a bit now with some
extra explanations inline\nto make it more understandable.\n\nNew tests
have been added to smoke test this scenario.\n\n### Checklist\n\nCheck
the PR satisfies following conditions. \n\n- [x] [Unit or
functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere
updated or added to match the most common
scenarios\n\n---------\n\nCo-authored-by: Nick Partridge
<nick.ryan.partridge@gmail.com>","sha":"48926e5173ebec2444a3ec6f244bbadb42eab3b0"}}]}]
BACKPORT-->
This commit is contained in:
Marco Liberati 2025-03-25 16:26:54 +01:00 committed by GitHub
parent 673c43bf3e
commit e163ff3c49
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 107 additions and 50 deletions

View file

@ -5,7 +5,6 @@
* 2.0.
*/
import { v4 as uuidv4 } from 'uuid';
import type { SavedObjectReference } from '@kbn/core/public';
import { EVENT_ANNOTATION_GROUP_TYPE } from '@kbn/event-annotation-common';
import { cloneDeep } from 'lodash';
@ -100,58 +99,68 @@ export function convertToPersistable(state: XYState) {
const persistableLayers: XYPersistedLayerConfig[] = [];
persistableState.layers.forEach((layer) => {
// anything but an annotation can just be persisted as is
if (!isAnnotationsLayer(layer)) {
persistableLayers.push(layer);
} else {
if (isByReferenceAnnotationsLayer(layer)) {
const referenceName = `ref-${uuidv4()}`;
savedObjectReferences.push({
type: EVENT_ANNOTATION_GROUP_TYPE,
id: layer.annotationGroupId,
name: referenceName,
});
if (!annotationLayerHasUnsavedChanges(layer)) {
const persistableLayer: XYPersistedByReferenceAnnotationLayerConfig = {
persistanceType: 'byReference',
layerId: layer.layerId,
layerType: layer.layerType,
annotationGroupRef: referenceName,
};
persistableLayers.push(persistableLayer);
} else {
const persistableLayer: XYPersistedLinkedByValueAnnotationLayerConfig = {
persistanceType: 'linked',
cachedMetadata: layer.cachedMetadata || {
title: layer.__lastSaved.title,
description: layer.__lastSaved.description,
tags: layer.__lastSaved.tags,
},
layerId: layer.layerId,
layerType: layer.layerType,
annotationGroupRef: referenceName,
annotations: layer.annotations,
ignoreGlobalFilters: layer.ignoreGlobalFilters,
};
persistableLayers.push(persistableLayer);
savedObjectReferences.push({
type: 'index-pattern',
id: layer.indexPatternId,
name: getLayerReferenceName(layer.layerId),
});
}
} else {
const { indexPatternId, ...persistableLayer } = layer;
savedObjectReferences.push({
type: 'index-pattern',
id: indexPatternId,
name: getLayerReferenceName(layer.layerId),
});
persistableLayers.push({ ...persistableLayer, persistanceType: 'byValue' });
}
return;
}
// a by value annotation layer can be persisted with some config tweak
if (!isByReferenceAnnotationsLayer(layer)) {
const { indexPatternId, ...persistableLayer } = layer;
savedObjectReferences.push({
type: 'index-pattern',
id: indexPatternId,
name: getLayerReferenceName(layer.layerId),
});
persistableLayers.push({ ...persistableLayer, persistanceType: 'byValue' });
return;
}
/**
* by reference annotation layer needs to be handled carefully
**/
// make this id stable so that it won't retrigger all the time a change diff
const referenceName = `ref-${layer.layerId}`;
savedObjectReferences.push({
type: EVENT_ANNOTATION_GROUP_TYPE,
id: layer.annotationGroupId,
name: referenceName,
});
// if there's no divergence from the library, it can be persisted without much ado
if (!annotationLayerHasUnsavedChanges(layer)) {
const persistableLayer: XYPersistedByReferenceAnnotationLayerConfig = {
persistanceType: 'byReference',
layerId: layer.layerId,
layerType: layer.layerType,
annotationGroupRef: referenceName,
};
persistableLayers.push(persistableLayer);
return;
}
// this is the case where the by reference diverged from library
// so it needs to persist some extra metadata
const persistableLayer: XYPersistedLinkedByValueAnnotationLayerConfig = {
persistanceType: 'linked',
cachedMetadata: layer.cachedMetadata || {
title: layer.__lastSaved.title,
description: layer.__lastSaved.description,
tags: layer.__lastSaved.tags,
},
layerId: layer.layerId,
layerType: layer.layerType,
annotationGroupRef: referenceName,
annotations: layer.annotations,
ignoreGlobalFilters: layer.ignoreGlobalFilters,
};
persistableLayers.push(persistableLayer);
savedObjectReferences.push({
type: 'index-pattern',
id: layer.indexPatternId,
name: getLayerReferenceName(layer.layerId),
});
});
return { savedObjectReferences, state: { ...persistableState, layers: persistableLayers } };
}

View file

@ -19,6 +19,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const dashboardPanelActions = getService('dashboardPanelActions');
const testSubjects = getService('testSubjects');
const elasticChart = getService('elasticChart');
const toastsService = getService('toasts');
const createNewLens = async () => {
await visualize.navigateToNewVisualization();
@ -229,6 +230,53 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await timeToVisualize.resetNewDashboard();
});
it('should allow adding a by reference annotation', async () => {
const ANNOTATION_GROUP_TITLE = 'My by reference annotation group';
await loadExistingLens();
await lens.save('xyVisChart Copy 2', true, false, false, 'new');
await dashboard.waitForRenderComplete();
await elasticChart.setNewChartUiDebugFlag(true);
await dashboardPanelActions.clickInlineEdit();
log.debug('Adds by reference annotation');
await lens.createLayer('annotations');
await lens.performLayerAction('lnsXY_annotationLayer_saveToLibrary', 1);
await visualize.setSaveModalValues(ANNOTATION_GROUP_TITLE, {
description: 'my description',
});
await testSubjects.click('confirmSaveSavedObjectButton');
const toastContents = await toastsService.getContentByIndex(1);
expect(toastContents).to.be(
`Saved "${ANNOTATION_GROUP_TITLE}"\nView or manage in the annotation library.`
);
// now close
await testSubjects.click('applyFlyoutButton');
log.debug('Edit the by reference annotation');
// and try to edit again the by reference annotation layer event
await dashboardPanelActions.clickInlineEdit();
expect((await find.allByCssSelector(`[data-test-subj^="lns-layerPanel-"]`)).length).to.eql(2);
expect(
await (
await testSubjects.find('lnsXY_xAnnotationsPanel > lns-dimensionTrigger')
).getVisibleText()
).to.eql('Event');
await dashboard.waitForRenderComplete();
await timeToVisualize.resetNewDashboard();
});
it('should allow adding a reference line', async () => {
await loadExistingLens();
await lens.save('xyVisChart Copy', true, false, false, 'new');