[Cases] Fix bug with registered attachmets memoization (#158441)

## Summary

In https://github.com/elastic/kibana/pull/156179 I fixed a bug with the
memorization of the react components of registered attachments in Cases.
This fix introduced another bug which I am fixing in this PR.

Fixes: https://github.com/elastic/kibana/issues/158447

### Bug


782a552c-29bd-4bae-b66b-8620cbdeb5ff

### Fix


a4834ce2-73c8-48cb-9ec4-f480133eca38

### Checklist

Delete any items that are not applicable to this PR.

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

### For maintainers

- [x] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

## Release notes

Fix a bug where ML embeddables, OsQuery, and IoCs attachments in a case
render the wrong view.
This commit is contained in:
Christos Nasikas 2023-05-26 15:43:43 +03:00 committed by GitHub
parent ad85cc0727
commit a2a0860f65
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 170 additions and 100 deletions

View file

@ -50,23 +50,27 @@ type BuilderArgs<C, R> = Pick<
/**
* Provides a render function for attachment type
*/
const getAttachmentRenderer = memoize(() => {
let AttachmentElement: React.ReactElement;
const getAttachmentRenderer = (cachingKey: string) =>
memoize(
() => {
let AttachmentElement: React.ReactElement;
const renderCallback = (attachmentViewObject: AttachmentViewObject, props: object) => {
if (!attachmentViewObject.children) return;
const renderCallback = (attachmentViewObject: AttachmentViewObject, props: object) => {
if (!attachmentViewObject.children) return;
if (!AttachmentElement) {
AttachmentElement = React.createElement(attachmentViewObject.children, props);
} else {
AttachmentElement = React.cloneElement(AttachmentElement, props);
}
if (!AttachmentElement) {
AttachmentElement = React.createElement(attachmentViewObject.children, props);
} else {
AttachmentElement = React.cloneElement(AttachmentElement, props);
}
return <Suspense fallback={<EuiLoadingSpinner />}>{AttachmentElement}</Suspense>;
};
return <Suspense fallback={<EuiLoadingSpinner />}>{AttachmentElement}</Suspense>;
};
return renderCallback;
});
return renderCallback;
},
() => cachingKey
);
export const createRegisteredAttachmentUserActionBuilder = <
C extends Comment,
@ -120,7 +124,7 @@ export const createRegisteredAttachmentUserActionBuilder = <
const attachmentViewObject = attachmentType.getAttachmentViewObject(props);
const renderer = getAttachmentRenderer();
const renderer = getAttachmentRenderer(userAction.id)();
const actions = attachmentViewObject.getActions?.(props) ?? [];
const [primaryActions, nonPrimaryActions] = partition(actions, 'isPrimary');
const visiblePrimaryActions = primaryActions.slice(0, 2);

View file

@ -11,6 +11,8 @@ import {
CommentType,
Case,
CommentRequest,
CommentRequestExternalReferenceType,
CommentRequestPersistableStateType,
} from '@kbn/cases-plugin/common/api';
import { expect } from 'expect';
import {
@ -85,16 +87,10 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
describe('Attachment framework', () => {
describe('External reference attachments', () => {
let caseWithAttachment: Case;
const externalReferenceAttachment = getExternalReferenceAttachment();
before(async () => {
caseWithAttachment = await createAttachmentAndNavigate({
type: CommentType.externalReference,
externalReferenceId: 'my-id',
externalReferenceStorage: { type: ExternalReferenceStorageType.elasticSearchDoc },
externalReferenceAttachmentTypeId: '.test',
externalReferenceMetadata: null,
owner: 'cases',
});
caseWithAttachment = await createAttachmentAndNavigate(externalReferenceAttachment);
});
after(async () => {
@ -109,77 +105,6 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
});
describe('Persistable state attachments', () => {
const getLensState = (dataViewId: string) => ({
title: '',
visualizationType: 'lnsXY',
type: 'lens',
references: [
{
type: 'index-pattern',
id: dataViewId,
name: 'indexpattern-datasource-layer-85863a23-73a0-4e11-9774-70f77b9a5898',
},
],
state: {
visualization: {
legend: { isVisible: true, position: 'right' },
valueLabels: 'hide',
fittingFunction: 'None',
axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true },
tickLabelsVisibilitySettings: { x: true, yLeft: true, yRight: true },
labelsOrientation: { x: 0, yLeft: 0, yRight: 0 },
gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true },
preferredSeriesType: 'bar_stacked',
layers: [
{
layerId: '85863a23-73a0-4e11-9774-70f77b9a5898',
accessors: ['63810bd4-8481-4aab-822a-532d8513a8b1'],
position: 'top',
seriesType: 'bar_stacked',
showGridlines: false,
layerType: 'data',
xAccessor: 'ab807e89-c453-415b-8eb4-3986de52c923',
},
],
},
query: { query: '', language: 'kuery' },
filters: [],
datasourceStates: {
formBased: {
layers: {
'85863a23-73a0-4e11-9774-70f77b9a5898': {
columns: {
'ab807e89-c453-415b-8eb4-3986de52c923': {
label: '@timestamp',
dataType: 'date',
operationType: 'date_histogram',
sourceField: '@timestamp',
isBucketed: true,
scale: 'interval',
params: { interval: 'auto', includeEmptyRows: true, dropPartials: false },
},
'63810bd4-8481-4aab-822a-532d8513a8b1': {
label: 'Median of id',
dataType: 'number',
operationType: 'median',
sourceField: 'id',
isBucketed: false,
scale: 'ratio',
params: { emptyAsNull: true },
},
},
columnOrder: [
'ab807e89-c453-415b-8eb4-3986de52c923',
'63810bd4-8481-4aab-822a-532d8513a8b1',
],
incompleteColumns: {},
},
},
},
},
},
});
let caseWithAttachment: Case;
let dataViewId = '';
@ -188,12 +113,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
const res = await createLogStashDataView(supertest);
dataViewId = res.data_view.id;
caseWithAttachment = await createAttachmentAndNavigate({
type: CommentType.persistableState,
persistableStateAttachmentTypeId: '.test',
persistableStateAttachmentState: getLensState(dataViewId),
owner: 'cases',
});
const persistableStateAttachment = getPersistableStateAttachment(dataViewId);
caseWithAttachment = await createAttachmentAndNavigate(persistableStateAttachment);
});
after(async () => {
@ -206,7 +127,65 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
const attachmentId = caseWithAttachment?.comments?.[0].id;
await validateAttachment(CommentType.persistableState, attachmentId);
await retry.waitFor(
'actions accordion to exist',
'persistable state to exist',
async () => await find.existsByCssSelector('.lnsExpressionRenderer')
);
});
});
describe('Multiple attachments', () => {
let originalCase: Case;
let dataViewId = '';
before(async () => {
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional');
const res = await createLogStashDataView(supertest);
dataViewId = res.data_view.id;
originalCase = await cases.api.createCase({
title: 'Registering multiple attachments',
});
const externalReferenceAttachment = getExternalReferenceAttachment();
const persistableStateAttachment = getPersistableStateAttachment(dataViewId);
await cases.api.createAttachment({
caseId: originalCase.id,
params: externalReferenceAttachment,
});
await cases.api.createAttachment({
caseId: originalCase.id,
params: persistableStateAttachment,
});
await cases.navigation.navigateToApp();
await cases.casesTable.waitForCasesToBeListed();
await cases.casesTable.goToFirstListedCase();
await header.waitUntilLoadingHasFinished();
});
after(async () => {
await cases.api.deleteAllCases();
await deleteLogStashDataView(supertest, dataViewId);
await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional');
});
it('renders multiple attachment types correctly', async () => {
const theCase = await getCase({
supertest,
caseId: originalCase.id,
includeComments: true,
});
const externalRefAttachmentId = theCase?.comments?.[0].id;
const persistableStateAttachmentId = theCase?.comments?.[1].id;
await validateAttachment(CommentType.externalReference, externalRefAttachmentId);
await validateAttachment(CommentType.persistableState, persistableStateAttachmentId);
await testSubjects.existOrFail('test-attachment-content');
await retry.waitFor(
'persistable state to exist',
async () => await find.existsByCssSelector('.lnsExpressionRenderer')
);
});
@ -348,3 +327,90 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
});
});
};
const getLensState = (dataViewId: string) => ({
title: '',
visualizationType: 'lnsXY',
type: 'lens',
references: [
{
type: 'index-pattern',
id: dataViewId,
name: 'indexpattern-datasource-layer-85863a23-73a0-4e11-9774-70f77b9a5898',
},
],
state: {
visualization: {
legend: { isVisible: true, position: 'right' },
valueLabels: 'hide',
fittingFunction: 'None',
axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true },
tickLabelsVisibilitySettings: { x: true, yLeft: true, yRight: true },
labelsOrientation: { x: 0, yLeft: 0, yRight: 0 },
gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true },
preferredSeriesType: 'bar_stacked',
layers: [
{
layerId: '85863a23-73a0-4e11-9774-70f77b9a5898',
accessors: ['63810bd4-8481-4aab-822a-532d8513a8b1'],
position: 'top',
seriesType: 'bar_stacked',
showGridlines: false,
layerType: 'data',
xAccessor: 'ab807e89-c453-415b-8eb4-3986de52c923',
},
],
},
query: { query: '', language: 'kuery' },
filters: [],
datasourceStates: {
formBased: {
layers: {
'85863a23-73a0-4e11-9774-70f77b9a5898': {
columns: {
'ab807e89-c453-415b-8eb4-3986de52c923': {
label: '@timestamp',
dataType: 'date',
operationType: 'date_histogram',
sourceField: '@timestamp',
isBucketed: true,
scale: 'interval',
params: { interval: 'auto', includeEmptyRows: true, dropPartials: false },
},
'63810bd4-8481-4aab-822a-532d8513a8b1': {
label: 'Median of id',
dataType: 'number',
operationType: 'median',
sourceField: 'id',
isBucketed: false,
scale: 'ratio',
params: { emptyAsNull: true },
},
},
columnOrder: [
'ab807e89-c453-415b-8eb4-3986de52c923',
'63810bd4-8481-4aab-822a-532d8513a8b1',
],
incompleteColumns: {},
},
},
},
},
},
});
const getExternalReferenceAttachment = (): CommentRequestExternalReferenceType => ({
type: CommentType.externalReference,
externalReferenceId: 'my-id',
externalReferenceStorage: { type: ExternalReferenceStorageType.elasticSearchDoc },
externalReferenceAttachmentTypeId: '.test',
externalReferenceMetadata: null,
owner: 'cases',
});
const getPersistableStateAttachment = (dataViewId: string): CommentRequestPersistableStateType => ({
type: CommentType.persistableState,
persistableStateAttachmentTypeId: '.test',
persistableStateAttachmentState: getLensState(dataViewId),
owner: 'cases',
});