Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { SECURITY_SOLUTION_OWNER, OBSERVABILITY_OWNER, GENERAL_CASES_OWNER } from './owners';

// ----------------Unified attachment types-------------------------
export const COMMENT_ATTACHMENT_TYPE = 'comment';
export const SECURITY_EVENT_ATTACHMENT_TYPE = 'security.event';
export const LENS_ATTACHMENT_TYPE = 'lens';

// ----------------Legacy attachment types-------------------------
export const LEGACY_ACTIONS_TYPE = 'actions';
Expand All @@ -19,6 +19,8 @@ export const LEGACY_EXTERNAL_REFERENCE_TYPE = 'externalReference';
export const LEGACY_PERSISTABLE_STATE_TYPE = 'persistableState';
export const LEGACY_USER_TYPE = 'user';

export const LEGACY_LENS_ATTACHMENT_TYPE = '.lens';

export const LEGACY_ATTACHMENT_TYPES = new Set([
LEGACY_ACTIONS_TYPE,
LEGACY_ALERT_TYPE,
Expand All @@ -33,6 +35,17 @@ export const UNIFIED_ATTACHMENT_TYPES = new Set([
SECURITY_EVENT_ATTACHMENT_TYPE,
]);

export const PERSISTABLE_STATE_LEGACY_TO_UNIFIED_MAP: Record<string, string> = {
[LEGACY_LENS_ATTACHMENT_TYPE]: LENS_ATTACHMENT_TYPE,
} as const;

export const PERSISTABLE_STATE_UNIFIED_TO_LEGACY_MAP: Record<string, string> = {
[LENS_ATTACHMENT_TYPE]: LEGACY_LENS_ATTACHMENT_TYPE,
} as const;

export const PERSISTABLE_ATTACHMENT_TYPES = new Set<string>(
Object.keys(PERSISTABLE_STATE_UNIFIED_TO_LEGACY_MAP)
);
/**
* Mapping from legacy attachment type names to unified names.
*/
Expand All @@ -54,6 +67,7 @@ export const UNIFIED_TO_LEGACY_MAP: Record<string, string> = {
export const MIGRATED_ATTACHMENT_TYPES = new Set<string>([
COMMENT_ATTACHMENT_TYPE,
SECURITY_EVENT_ATTACHMENT_TYPE,
...PERSISTABLE_ATTACHMENT_TYPES,
]);

export const OWNER_TO_PREFIX_MAP: Partial<Record<string, string>> = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ export * from './files';
export * from './application';
export * from './observables';
export * from './attachments';
export { LENS_ATTACHMENT_TYPE } from './visualizations';

/**
* Cases connector limits.
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@
* 2.0.
*/

import { LEGACY_LENS_ATTACHMENT_TYPE, LENS_ATTACHMENT_TYPE } from '../../constants/attachments';
import { AttachmentType } from '../../types/domain';
import { SECURITY_SOLUTION_OWNER } from '../../constants';
import { isMigratedAttachmentType, toUnifiedAttachmentType } from './migration_utils';
import {
isMigratedAttachmentType,
isPersistableType,
toUnifiedAttachmentType,
} from './migration_utils';

const owner = SECURITY_SOLUTION_OWNER;

Expand All @@ -23,6 +28,10 @@ describe('migration_utils', () => {
expect(isMigratedAttachmentType('comment', owner)).toBe(true);
expect(isMigratedAttachmentType('security.event', 'security')).toBe(true);
});
it('is true for legacy and unified Lens persistable subtype ids', () => {
expect(isMigratedAttachmentType(LEGACY_LENS_ATTACHMENT_TYPE, owner)).toBe(true);
expect(isMigratedAttachmentType(LENS_ATTACHMENT_TYPE, owner)).toBe(true);
});

it('is false for non-migrated attachment types', () => {
expect(isMigratedAttachmentType(AttachmentType.alert, owner)).toBe(false);
Expand All @@ -37,4 +46,15 @@ describe('migration_utils', () => {
);
});
});

describe('isPersistableType', () => {
it('is true for Lens legacy and unified subtype ids', () => {
expect(isPersistableType(LEGACY_LENS_ATTACHMENT_TYPE)).toBe(true);
expect(isPersistableType(LENS_ATTACHMENT_TYPE)).toBe(true);
});

it('is false for unrelated persistable subtype ids', () => {
expect(isPersistableType('.test')).toBe(false);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,28 @@
import {
LEGACY_TO_UNIFIED_MAP,
MIGRATED_ATTACHMENT_TYPES,
PERSISTABLE_STATE_LEGACY_TO_UNIFIED_MAP,
PERSISTABLE_STATE_UNIFIED_TO_LEGACY_MAP,
PERSISTABLE_ATTACHMENT_TYPES,
UNIFIED_TO_LEGACY_MAP,
OWNER_TO_PREFIX_MAP,
LEGACY_EVENT_TYPE,
} from '../../constants/attachments';

export const isMigratedAttachmentType = (type: string, owner: string): boolean => {
return MIGRATED_ATTACHMENT_TYPES.has(toUnifiedAttachmentType(type, owner));
return (
MIGRATED_ATTACHMENT_TYPES.has(toUnifiedAttachmentType(type, owner)) ||
MIGRATED_ATTACHMENT_TYPES.has(toUnifiedPersistableStateAttachmentType(type))
);
};

export const toLegacyAttachmentType = (type?: string): string | undefined => {
if (typeof type !== 'string') {
return undefined;
}
if (type in PERSISTABLE_STATE_UNIFIED_TO_LEGACY_MAP) {
return toLegacyPersistableStateAttachmentType(type);
}
return UNIFIED_TO_LEGACY_MAP[type] ?? type;
};

Expand All @@ -34,3 +43,18 @@ export const toUnifiedAttachmentType = (type: string, owner: string): string =>
}
return LEGACY_TO_UNIFIED_MAP[type] ?? type;
};

/**
* True when the persistable-state subtype id (legacy `.lens` or unified `lens`) is one
* that this stack migrates to unified attachment attributes (currently Lens only).
*/
export const isPersistableType = (type: string): boolean =>
PERSISTABLE_ATTACHMENT_TYPES.has(toUnifiedPersistableStateAttachmentType(type));

export const toUnifiedPersistableStateAttachmentType = (type: string): string => {
return PERSISTABLE_STATE_LEGACY_TO_UNIFIED_MAP[type] ?? type;
};

export const toLegacyPersistableStateAttachmentType = (type: string): string => {
return PERSISTABLE_STATE_UNIFIED_TO_LEGACY_MAP[type] ?? type;
};
5 changes: 5 additions & 0 deletions x-pack/platform/plugins/shared/cases/public/api/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import type {
import {
isCommentRequestTypeExternalReference,
isCommentRequestTypePersistableState,
isUnifiedAttachmentRequest,
} from '../../common/utils/attachments';
import { isCommentUserAction } from '../../common/utils/user_actions';
import type {
Expand Down Expand Up @@ -103,6 +104,10 @@ export const convertAttachmentToCamelCase = (attachment: AttachmentRequestV2): A
return convertAttachmentToCamelExceptProperty(attachment, 'persistableStateAttachmentState');
}

if (isUnifiedAttachmentRequest(attachment)) {
return convertAttachmentToCamelExceptProperty(attachment, 'data');
}

return convertToCamelCase<AttachmentRequestV2, AttachmentUIV2>(attachment);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,16 @@
*/

import type { ExternalReferenceAttachmentTypeRegistry } from '../../client/attachment_framework/external_reference_registry';
import type { PersistableStateAttachmentTypeRegistry } from '../../client/attachment_framework/persistable_state_registry';
import type { UnifiedAttachmentTypeRegistry } from '../../client/attachment_framework/unified_attachment_registry';
import { getCommentAttachmentType } from './comment';
import { getFileType } from './file/file_type';
import { getVisualizationAttachmentType } from './lens/attachment';
import { getVisualizationAttachmentType } from './lens';

export const registerInternalAttachments = (
externalRefRegistry: ExternalReferenceAttachmentTypeRegistry,
persistableStateRegistry: PersistableStateAttachmentTypeRegistry,
unifiedRegistry: UnifiedAttachmentTypeRegistry
) => {
externalRefRegistry.register(getFileType());
persistableStateRegistry.register(getVisualizationAttachmentType());
unifiedRegistry.register(getVisualizationAttachmentType());
unifiedRegistry.register(getCommentAttachmentType());
};
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { useKibana } from '../../../../common/lib/kibana';
import { waitFor } from '@testing-library/react';
import { openModal } from './open_modal';
import type { CasesActionContextProps } from './types';
import { LENS_ATTACHMENT_TYPE } from '../../../../../common/constants/attachments';

const element = document.createElement('div');
document.body.appendChild(element);
Expand Down Expand Up @@ -99,15 +100,16 @@ describe('openModal', () => {
const res = getAttachments();
expect(res).toEqual([
{
persistableStateAttachmentState: {
attributes: mockLensAttributes,
timeRange: {
from: '2023-12-31T00:00:00.000Z',
to: '2024-01-01T00:00:00.000Z',
type: LENS_ATTACHMENT_TYPE,
data: {
state: {
attributes: mockLensAttributes,
timeRange: {
from: '2023-12-31T00:00:00.000Z',
to: '2024-01-01T00:00:00.000Z',
},
},
},
persistableStateAttachmentTypeId: '.lens',
type: 'persistableState',
},
]);
});
Expand Down Expand Up @@ -188,15 +190,16 @@ describe('openModal', () => {
const res = getAttachments();
expect(res).toEqual([
{
persistableStateAttachmentState: {
attributes: mockLensAttributes,
timeRange: {
from: '2024-01-09T00:00:00.000Z',
to: '2024-01-10T00:00:00.000Z',
type: LENS_ATTACHMENT_TYPE,
data: {
state: {
attributes: mockLensAttributes,
timeRange: {
from: '2024-01-09T00:00:00.000Z',
to: '2024-01-10T00:00:00.000Z',
},
},
},
persistableStateAttachmentTypeId: '.lens',
type: 'persistableState',
},
]);
});
Expand All @@ -218,15 +221,16 @@ describe('openModal', () => {

expect(res).toEqual([
{
persistableStateAttachmentState: {
attributes: mockLensAttributes,
timeRange: {
from: '2023-12-01T00:00:00.000Z',
to: '2024-01-01T00:00:00.000Z',
type: LENS_ATTACHMENT_TYPE,
data: {
state: {
attributes: mockLensAttributes,
timeRange: {
from: '2023-12-01T00:00:00.000Z',
to: '2024-01-01T00:00:00.000Z',
},
},
},
persistableStateAttachmentTypeId: '.lens',
type: 'persistableState',
},
]);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ describe('utils', () => {
// @ts-expect-error: extra attributes are not needed
expect(getLensCaseAttachment(embeddable)).toMatchInlineSnapshot(`
Object {
"persistableStateAttachmentState": Object {
"attributes": Object {},
"metadata": undefined,
"timeRange": Object {},
"data": Object {
"state": Object {
"attributes": Object {},
"metadata": undefined,
"timeRange": Object {},
},
},
"persistableStateAttachmentTypeId": ".lens",
"type": "persistableState",
"type": "lens",
}
`);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@
* 2.0.
*/
import type { LensSavedObjectAttributes } from '@kbn/lens-plugin/public';
import { LENS_ATTACHMENT_TYPE } from '../../../../../common/constants/visualizations';
import type { PersistableStateAttachmentPayload } from '../../../../../common/types/domain';
import { AttachmentType } from '../../../../../common/types/domain';
import { LENS_ATTACHMENT_TYPE } from '../../../../../common/constants/attachments';
import type { UnifiedValueAttachmentPayload } from '../../../../../common/types/domain';
import type { LensProps } from '../types';

type PersistableStateAttachmentWithoutOwner = Omit<PersistableStateAttachmentPayload, 'owner'>;
type UnifiedValueAttachmentWithoutOwner = Omit<UnifiedValueAttachmentPayload, 'owner'>;

export const getLensCaseAttachment = ({
timeRange,
Expand All @@ -20,9 +19,10 @@ export const getLensCaseAttachment = ({
timeRange: LensProps['timeRange'];
attributes: LensSavedObjectAttributes;
metadata?: LensProps['metadata'];
}): PersistableStateAttachmentWithoutOwner =>
}): UnifiedValueAttachmentWithoutOwner =>
({
persistableStateAttachmentState: { attributes, timeRange, metadata },
persistableStateAttachmentTypeId: LENS_ATTACHMENT_TYPE,
type: AttachmentType.persistableState,
} as unknown as PersistableStateAttachmentWithoutOwner);
type: LENS_ATTACHMENT_TYPE,
data: {
state: { attributes, timeRange, metadata },
},
} as unknown as UnifiedValueAttachmentWithoutOwner);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think there is a way we can fix this type cast?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lens data contains LensSavedObjectAttributes (from @kbn/lens-plugin/public) which is a deeply nested type with specific Lens-domain properties -- these are structurally incompatible with JsonValue even though they serialize to JSON just fine at runtime.

Since { state: { attributes: LensSavedObjectAttributes, ... } } is not assignable to Record<string, JsonValue>, TypeScript requires the unknown intermediary. There is no way to avoid this without changing either the Lens types or the UnifiedValueAttachmentPayload type definition.

Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
import React, { Suspense } from 'react';
import { screen, waitFor } from '@testing-library/react';
import { LENS_ATTACHMENT_TYPE } from '../../../../common';
import type { PersistableStateAttachmentViewProps } from '../../../client/attachment_framework/types';
import type { UnifiedValueAttachmentViewProps } from '../../../client/attachment_framework/types';
import { AttachmentActionType } from '../../../client/attachment_framework/types';

import { basicCase } from '../../../containers/mock';
import { getVisualizationAttachmentType } from './attachment';
import { getVisualizationAttachmentType } from '.';
import { createStartServicesMock } from '../../../common/lib/kibana/kibana_react.mock';
import { renderWithTestingProviders } from '../../../common/mock';

Expand All @@ -21,15 +21,27 @@ describe('getVisualizationAttachmentType', () => {
.fn()
.mockReturnValue(<div data-test-subj="embeddableComponent" />);

const attachmentViewProps: PersistableStateAttachmentViewProps = {
persistableStateAttachmentTypeId: LENS_ATTACHMENT_TYPE,
persistableStateAttachmentState: {
attributes: { state: { query: {} } },
timeRange: {},
const attachmentViewProps: UnifiedValueAttachmentViewProps = {
type: LENS_ATTACHMENT_TYPE,
data: {
state: {
attributes: { state: { query: {} } },
timeRange: {},
},
},
owner: 'securitySolution',
createdBy: { username: 'elastic', full_name: null, email: null, profile_uid: null },
version: '1',
savedObjectId: 'test',
caseData: { title: basicCase.title, id: basicCase.id },
};
rowContext: {
appId: 'cases',
manageMarkdownEditIds: [],
selectedOutlineCommentId: '',
loadingCommentIds: [],
euiTheme: {} as never,
},
} as unknown as UnifiedValueAttachmentViewProps;

beforeEach(() => {
jest.clearAllMocks();
Expand Down
Loading
Loading