[SecuritySolution] Decouple more timeline saved objects types from their API representations (#159398)

## Summary

Continuation of https://github.com/elastic/kibana/pull/158566. Tracked
in https://github.com/elastic/security-team/issues/6479.

In this PR we're separating the types for the `Note` and `PinnedEvent`
saved object and the type of it's API response equivalent. Doing so will
allow us to make changes to either representations individually which is
necessary for versioning the SO and routes (individually) in the future.

The saved object definition and their representative API equivalent now
live in `*/saved_object.ts` and `*/api.ts` respectively. A clean cut of
these two, now distinct types. You will encounter some duplication of
types in these files which is unavoidable. In the future, depending on
how both representations evolve when versioned, these two definitions
will diverge.

You will notice that only few types (and values) defined in
`saved_object.ts` are exported. They are only used for the conversion
logic. They are not exported so they're not accidentally required by
frontend or server code that is not dealing with the conversion. The
exported types all start with `SavedObject*` to clearly mark them as SO
representations.

The conversion files have been updated to use the new representations
and there is no implicit conversion between them (e.g. through spreading
or rest parameters).

The bulk of the changes are updates of import statements to change the
import to `**/api.ts`.


### Checklist

- [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
This commit is contained in:
Jan Monschke 2023-06-13 13:49:23 +02:00 committed by GitHub
parent 160fa420ef
commit 0c4906af89
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 420 additions and 378 deletions

View file

@ -12,8 +12,8 @@ import { stringEnum, unionWithNullType } from '../../utility_types';
import type { Maybe } from '../../search_strategy';
import { Direction } from '../../search_strategy';
import type { PinnedEvent } from './pinned_event';
import { PinnedEventToReturnSavedObjectRuntimeType } from './pinned_event';
import type { PinnedEvent } from './pinned_event/api';
import { PinnedEventRuntimeType } from './pinned_event/api';
import {
SavedObjectResolveAliasPurpose,
SavedObjectResolveAliasTargetId,
@ -24,8 +24,8 @@ import {
success_count as successCount,
} from '../../detection_engine/schemas/common/schemas';
import { errorSchema } from '../../detection_engine/schemas/response/error_schema';
import type { NoteResult } from './note';
import { NoteSavedObjectToReturnRuntimeType } from './note';
import type { Note } from './note/api';
import { NoteRuntimeType } from './note/api';
/*
* ColumnHeader Types
@ -320,11 +320,11 @@ export const TimelineSavedToReturnObjectRuntimeType = runtimeTypes.intersection(
version: runtimeTypes.string,
}),
runtimeTypes.partial({
eventIdToNoteIds: runtimeTypes.array(NoteSavedObjectToReturnRuntimeType),
eventIdToNoteIds: runtimeTypes.array(NoteRuntimeType),
noteIds: runtimeTypes.array(runtimeTypes.string),
notes: runtimeTypes.array(NoteSavedObjectToReturnRuntimeType),
notes: runtimeTypes.array(NoteRuntimeType),
pinnedEventIds: runtimeTypes.array(runtimeTypes.string),
pinnedEventsSaveObject: runtimeTypes.array(PinnedEventToReturnSavedObjectRuntimeType),
pinnedEventsSaveObject: runtimeTypes.array(PinnedEventRuntimeType),
}),
]);
@ -437,8 +437,8 @@ export const sortTimeline = runtimeTypes.type({
* Import/export timelines
*/
export type ExportedGlobalNotes = Array<Exclude<NoteResult, 'eventId'>>;
export type ExportedEventNotes = NoteResult[];
export type ExportedGlobalNotes = Array<Exclude<Note, 'eventId'>>;
export type ExportedEventNotes = Note[];
export interface ExportedNotes {
eventNotes: ExportedEventNotes;
@ -606,7 +606,7 @@ export interface TimelineResult {
dateRange?: Maybe<DateRangePickerResult>;
description?: Maybe<string>;
eqlOptions?: Maybe<EqlOptionsResult>;
eventIdToNoteIds?: Maybe<NoteResult[]>;
eventIdToNoteIds?: Maybe<Note[]>;
eventType?: Maybe<string>;
excludedRowRendererIds?: Maybe<RowRendererId[]>;
favorite?: Maybe<FavoriteTimelineResult[]>;
@ -614,7 +614,7 @@ export interface TimelineResult {
kqlMode?: Maybe<string>;
kqlQuery?: Maybe<SerializedFilterQueryResult>;
indexNames?: Maybe<string[]>;
notes?: Maybe<NoteResult[]>;
notes?: Maybe<Note[]>;
noteIds?: Maybe<string[]>;
pinnedEventIds?: Maybe<string[]>;
pinnedEventsSaveObject?: Maybe<PinnedEvent[]>;

View file

@ -0,0 +1,56 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
/* eslint-disable @typescript-eslint/no-empty-interface */
import * as runtimeTypes from 'io-ts';
import type { Maybe } from '../../../search_strategy/common';
import { unionWithNullType } from '../../../utility_types';
export const BareNoteSchema = runtimeTypes.intersection([
runtimeTypes.type({
timelineId: unionWithNullType(runtimeTypes.string),
}),
runtimeTypes.partial({
eventId: unionWithNullType(runtimeTypes.string),
note: unionWithNullType(runtimeTypes.string),
created: unionWithNullType(runtimeTypes.number),
createdBy: unionWithNullType(runtimeTypes.string),
updated: unionWithNullType(runtimeTypes.number),
updatedBy: unionWithNullType(runtimeTypes.string),
}),
]);
export interface BareNote extends runtimeTypes.TypeOf<typeof BareNoteSchema> {}
/**
* This type represents a note type stored in a saved object that does not include any fields that reference
* other saved objects.
*/
export type BareNoteWithoutExternalRefs = Omit<BareNote, 'timelineId'>;
export const NoteRuntimeType = runtimeTypes.intersection([
BareNoteSchema,
runtimeTypes.type({
noteId: runtimeTypes.string,
version: runtimeTypes.string,
}),
runtimeTypes.partial({
timelineVersion: unionWithNullType(runtimeTypes.string),
}),
]);
export interface Note extends runtimeTypes.TypeOf<typeof NoteRuntimeType> {}
export interface ResponseNote {
code?: Maybe<number>;
message?: Maybe<string>;
note: Note;
}

View file

@ -1,150 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
/* eslint-disable @typescript-eslint/no-empty-interface */
import * as runtimeTypes from 'io-ts';
import type { Maybe } from '../../../search_strategy/common';
import { Direction } from '../../../search_strategy/common';
import { unionWithNullType } from '../../../utility_types';
/*
* Note Types
*/
export const SavedNoteRuntimeType = runtimeTypes.intersection([
runtimeTypes.type({
timelineId: unionWithNullType(runtimeTypes.string),
}),
runtimeTypes.partial({
eventId: unionWithNullType(runtimeTypes.string),
note: unionWithNullType(runtimeTypes.string),
created: unionWithNullType(runtimeTypes.number),
createdBy: unionWithNullType(runtimeTypes.string),
updated: unionWithNullType(runtimeTypes.number),
updatedBy: unionWithNullType(runtimeTypes.string),
}),
]);
export interface SavedNote extends runtimeTypes.TypeOf<typeof SavedNoteRuntimeType> {}
/**
* This type represents a note type stored in a saved object that does not include any fields that reference
* other saved objects.
*/
export type NoteWithoutExternalRefs = Omit<SavedNote, 'timelineId'>;
/**
* Note Saved object type with metadata
*/
export const NoteSavedObjectRuntimeType = runtimeTypes.intersection([
runtimeTypes.type({
id: runtimeTypes.string,
attributes: SavedNoteRuntimeType,
version: runtimeTypes.string,
}),
runtimeTypes.partial({
noteId: runtimeTypes.string,
timelineVersion: runtimeTypes.union([
runtimeTypes.string,
runtimeTypes.null,
runtimeTypes.undefined,
]),
}),
]);
export const NoteSavedObjectToReturnRuntimeType = runtimeTypes.intersection([
SavedNoteRuntimeType,
runtimeTypes.type({
noteId: runtimeTypes.string,
version: runtimeTypes.string,
}),
runtimeTypes.partial({
timelineVersion: unionWithNullType(runtimeTypes.string),
}),
]);
export interface NoteSavedObject
extends runtimeTypes.TypeOf<typeof NoteSavedObjectToReturnRuntimeType> {}
export enum SortFieldNote {
updatedBy = 'updatedBy',
updated = 'updated',
}
export const pageInfoNoteRt = runtimeTypes.type({
pageIndex: runtimeTypes.number,
pageSize: runtimeTypes.number,
});
export type PageInfoNote = runtimeTypes.TypeOf<typeof pageInfoNoteRt>;
export const sortNoteRt = runtimeTypes.type({
sortField: runtimeTypes.union([
runtimeTypes.literal(SortFieldNote.updatedBy),
runtimeTypes.literal(SortFieldNote.updated),
]),
sortOrder: runtimeTypes.union([
runtimeTypes.literal(Direction.asc),
runtimeTypes.literal(Direction.desc),
]),
});
export type SortNote = runtimeTypes.TypeOf<typeof sortNoteRt>;
export const NoteServerRepresentation = runtimeTypes.intersection([
runtimeTypes.type({
timelineId: unionWithNullType(runtimeTypes.string),
}),
runtimeTypes.partial({
eventId: unionWithNullType(runtimeTypes.string),
note: unionWithNullType(runtimeTypes.string),
created: unionWithNullType(runtimeTypes.number),
createdBy: unionWithNullType(runtimeTypes.string),
updated: unionWithNullType(runtimeTypes.number),
updatedBy: unionWithNullType(runtimeTypes.string),
}),
]);
export type NoteServerRepresentationType = runtimeTypes.TypeOf<typeof NoteServerRepresentation>;
export interface NoteResult {
eventId?: Maybe<string>;
note?: Maybe<string>;
timelineId?: Maybe<string>;
noteId: string;
created?: Maybe<number>;
createdBy?: Maybe<string>;
timelineVersion?: Maybe<string>;
updated?: Maybe<number>;
updatedBy?: Maybe<string>;
version?: Maybe<string>;
}
export interface ResponseNotes {
notes: NoteResult[];
totalCount?: Maybe<number>;
}
export interface ResponseNote {
code?: Maybe<number>;
message?: Maybe<string>;
note: NoteResult;
}

View file

@ -0,0 +1,56 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
/* eslint-disable @typescript-eslint/no-empty-interface */
import * as runtimeTypes from 'io-ts';
import { unionWithNullType } from '../../../utility_types';
/*
* Note Types
*/
const SavedNoteRuntimeType = runtimeTypes.intersection([
runtimeTypes.type({
timelineId: unionWithNullType(runtimeTypes.string),
}),
runtimeTypes.partial({
eventId: unionWithNullType(runtimeTypes.string),
note: unionWithNullType(runtimeTypes.string),
created: unionWithNullType(runtimeTypes.number),
createdBy: unionWithNullType(runtimeTypes.string),
updated: unionWithNullType(runtimeTypes.number),
updatedBy: unionWithNullType(runtimeTypes.string),
}),
]);
/**
* Note Saved object type with metadata
*/
export const SavedObjectNoteRuntimeType = runtimeTypes.intersection([
runtimeTypes.type({
id: runtimeTypes.string,
attributes: SavedNoteRuntimeType,
version: runtimeTypes.string,
}),
runtimeTypes.partial({
noteId: runtimeTypes.string,
timelineVersion: runtimeTypes.union([
runtimeTypes.string,
runtimeTypes.null,
runtimeTypes.undefined,
]),
}),
]);
interface SavedObjectNote extends runtimeTypes.TypeOf<typeof SavedNoteRuntimeType> {}
/**
* This type represents a note type stored in a saved object that does not include any fields that reference
* other saved objects.
*/
export type SavedObjectNoteWithoutExternalRefs = Omit<SavedObjectNote, 'timelineId'>;

View file

@ -0,0 +1,51 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
/* eslint-disable @typescript-eslint/no-empty-interface */
import * as runtimeTypes from 'io-ts';
import { unionWithNullType } from '../../../utility_types';
/*
* Note Types
*/
export const BarePinnedEventType = runtimeTypes.intersection([
runtimeTypes.type({
timelineId: runtimeTypes.string,
eventId: runtimeTypes.string,
}),
runtimeTypes.partial({
created: unionWithNullType(runtimeTypes.number),
createdBy: unionWithNullType(runtimeTypes.string),
updated: unionWithNullType(runtimeTypes.number),
updatedBy: unionWithNullType(runtimeTypes.string),
}),
]);
export interface BarePinnedEvent extends runtimeTypes.TypeOf<typeof BarePinnedEventType> {}
/**
* This type represents a pinned event type stored in a saved object that does not include any fields that reference
* other saved objects.
*/
export type BarePinnedEventWithoutExternalRefs = Omit<BarePinnedEvent, 'timelineId'>;
export const PinnedEventRuntimeType = runtimeTypes.intersection([
runtimeTypes.type({
pinnedEventId: runtimeTypes.string,
version: runtimeTypes.string,
}),
BarePinnedEventType,
runtimeTypes.partial({
timelineVersion: unionWithNullType(runtimeTypes.string),
}),
]);
export interface PinnedEvent extends runtimeTypes.TypeOf<typeof PinnedEventRuntimeType> {}
export type PinnedEventResponse = PinnedEvent & { code: number; message?: string };

View file

@ -1,91 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
/* eslint-disable @typescript-eslint/no-empty-interface */
import * as runtimeTypes from 'io-ts';
import type { Maybe } from '../../../search_strategy/common';
import { unionWithNullType } from '../../../utility_types';
/*
* Note Types
*/
export const SavedPinnedEventRuntimeType = runtimeTypes.intersection([
runtimeTypes.type({
timelineId: runtimeTypes.string,
eventId: runtimeTypes.string,
}),
runtimeTypes.partial({
created: unionWithNullType(runtimeTypes.number),
createdBy: unionWithNullType(runtimeTypes.string),
updated: unionWithNullType(runtimeTypes.number),
updatedBy: unionWithNullType(runtimeTypes.string),
}),
]);
export interface SavedPinnedEvent extends runtimeTypes.TypeOf<typeof SavedPinnedEventRuntimeType> {}
/**
* This type represents a pinned event type stored in a saved object that does not include any fields that reference
* other saved objects.
*/
export type PinnedEventWithoutExternalRefs = Omit<SavedPinnedEvent, 'timelineId'>;
/**
* Note Saved object type with metadata
*/
export const PinnedEventSavedObjectRuntimeType = runtimeTypes.intersection([
runtimeTypes.type({
id: runtimeTypes.string,
attributes: SavedPinnedEventRuntimeType,
version: runtimeTypes.string,
}),
runtimeTypes.partial({
pinnedEventId: unionWithNullType(runtimeTypes.string),
timelineVersion: unionWithNullType(runtimeTypes.string),
}),
]);
export const PinnedEventToReturnSavedObjectRuntimeType = runtimeTypes.intersection([
runtimeTypes.type({
pinnedEventId: runtimeTypes.string,
version: runtimeTypes.string,
}),
SavedPinnedEventRuntimeType,
runtimeTypes.partial({
timelineVersion: unionWithNullType(runtimeTypes.string),
}),
]);
export interface PinnedEventSavedObject
extends runtimeTypes.TypeOf<typeof PinnedEventToReturnSavedObjectRuntimeType> {}
export interface PinnedEvent {
code?: Maybe<number>;
message?: Maybe<string>;
pinnedEventId: string;
eventId?: Maybe<string>;
timelineId?: Maybe<string>;
timelineVersion?: Maybe<string>;
created?: Maybe<number>;
createdBy?: Maybe<string>;
updated?: Maybe<number>;
updatedBy?: Maybe<string>;
version?: Maybe<string>;
}

View file

@ -0,0 +1,51 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
/* eslint-disable @typescript-eslint/no-empty-interface */
import * as runtimeTypes from 'io-ts';
import { unionWithNullType } from '../../../utility_types';
/*
* Note Types
*/
const SavedPinnedEventType = runtimeTypes.intersection([
runtimeTypes.type({
timelineId: runtimeTypes.string,
eventId: runtimeTypes.string,
}),
runtimeTypes.partial({
created: unionWithNullType(runtimeTypes.number),
createdBy: unionWithNullType(runtimeTypes.string),
updated: unionWithNullType(runtimeTypes.number),
updatedBy: unionWithNullType(runtimeTypes.string),
}),
]);
/**
* Pinned Event Saved object type with metadata
*/
export const SavedObjectPinnedEventRuntimeType = runtimeTypes.intersection([
runtimeTypes.type({
id: runtimeTypes.string,
attributes: SavedPinnedEventType,
version: runtimeTypes.string,
}),
runtimeTypes.partial({
pinnedEventId: unionWithNullType(runtimeTypes.string),
timelineVersion: unionWithNullType(runtimeTypes.string),
}),
]);
export interface SavedObjectPinnedEvent extends runtimeTypes.TypeOf<typeof SavedPinnedEventType> {}
/**
* This type represents a pinned event type stored in a saved object that does not include any fields that reference
* other saved objects.
*/
export type SavedObjectPinnedEventWithoutExternalRefs = Omit<SavedObjectPinnedEvent, 'timelineId'>;

View file

@ -9,7 +9,7 @@ import type { ISearchStart } from '@kbn/data-plugin/public';
import type { Filter } from '@kbn/es-query';
import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs';
import type { Status } from '../../../../common/detection_engine/schemas/common/schemas';
import type { NoteResult } from '../../../../common/types/timeline/note';
import type { Note } from '../../../../common/types/timeline/note/api';
import type { DataProvider } from '../../../timelines/components/timeline/data_providers/data_provider';
import type { TimelineModel } from '../../../timelines/store/timeline/model';
import type { inputsModel } from '../../../common/store';
@ -65,7 +65,7 @@ export interface CreateTimelineProps {
from: string;
timeline: TimelineModel;
to: string;
notes: NoteResult[] | null;
notes: Note[] | null;
ruleNote?: string;
ruleAuthor?: string | string[];
}

View file

@ -847,6 +847,8 @@ describe('helpers', () => {
updated: 1585233356356,
noteId: 'note-id',
note: 'I am a note',
timelineId: null,
version: 'testVersion',
},
],
})();
@ -864,7 +866,7 @@ describe('helpers', () => {
user: 'unknown',
saveObjectId: 'note-id',
timelineId: null,
version: undefined,
version: 'testVersion',
},
],
});

View file

@ -69,8 +69,8 @@ import {
DEFAULT_TO_MOMENT,
} from '../../../common/utils/default_date_settings';
import { resolveTimeline } from '../../containers/api';
import type { PinnedEvent } from '../../../../common/types/timeline/pinned_event';
import type { NoteResult } from '../../../../common/types/timeline/note';
import type { PinnedEvent } from '../../../../common/types/timeline/pinned_event/api';
import type { Note } from '../../../../common/types/timeline/note/api';
export const OPEN_TIMELINE_CLASS_NAME = 'open-timeline';
@ -147,10 +147,7 @@ const setTimelineFilters = (filter: FilterTimelineResult) => ({
...(filter.script != null ? { exists: parseString(filter.script) } : {}),
});
const setEventIdToNoteIds = (
duplicate: boolean,
eventIdToNoteIds: NoteResult[] | null | undefined
) =>
const setEventIdToNoteIds = (duplicate: boolean, eventIdToNoteIds: Note[] | null | undefined) =>
duplicate
? {}
: eventIdToNoteIds != null
@ -309,7 +306,7 @@ export const formatTimelineResultToModel = (
timelineToOpen: TimelineResult,
duplicate: boolean = false,
timelineType?: TimelineType
): { notes: NoteResult[] | null | undefined; timeline: TimelineModel } => {
): { notes: Note[] | null | undefined; timeline: TimelineModel } => {
const { notes, ...timelineModel } = timelineToOpen;
return {
notes,
@ -474,7 +471,7 @@ export const dispatchUpdateTimeline =
dispatchAddNotes({
notes:
notes != null
? notes.map((note: NoteResult) => ({
? notes.map((note: Note) => ({
created: note.created != null ? new Date(note.created) : new Date(),
id: note.noteId,
lastEdit: note.updated != null ? new Date(note.updated) : new Date(),

View file

@ -8,7 +8,7 @@
import type React from 'react';
import type { AllTimelinesVariables } from '../../containers/all';
import type { TimelineModel } from '../../store/timeline/model';
import type { NoteResult } from '../../../../common/types/timeline/note';
import type { Note } from '../../../../common/types/timeline/note/api';
import type {
RowRendererId,
SingleTimelineResolveResponse,
@ -219,7 +219,7 @@ export interface UpdateTimeline {
id: string;
forceNotes?: boolean;
from: string;
notes: NoteResult[] | null | undefined;
notes: Note[] | null | undefined;
resolveTimelineConfig?: ResolveTimelineConfig;
timeline: TimelineModel;
to: string;

View file

@ -6,7 +6,7 @@
*/
import { NOTE_URL } from '../../../../common/constants';
import type { NoteSavedObject, SavedNote } from '../../../../common/types/timeline/note';
import type { BareNote, Note } from '../../../../common/types/timeline/note/api';
import { KibanaServices } from '../../../common/lib/kibana';
export const persistNote = async ({
@ -15,7 +15,7 @@ export const persistNote = async ({
version,
overrideOwner,
}: {
note: SavedNote;
note: BareNote;
noteId?: string | null;
version?: string | null;
overrideOwner?: boolean;
@ -27,7 +27,7 @@ export const persistNote = async ({
} catch (err) {
return Promise.reject(new Error(`Failed to stringify query: ${JSON.stringify(err)}`));
}
const response = await KibanaServices.get().http.patch<NoteSavedObject[]>(NOTE_URL, {
const response = await KibanaServices.get().http.patch<Note[]>(NOTE_URL, {
method: 'PATCH',
body: requestBody,
});

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { PINNED_EVENT_URL } from '../../../../common/constants';
import type { PinnedEvent } from '../../../../common/types/timeline/pinned_event';
import type { PinnedEvent } from '../../../../common/types/timeline/pinned_event/api';
import { KibanaServices } from '../../../common/lib/kibana';
export const persistPinnedEvent = async ({

View file

@ -28,7 +28,7 @@ import { myEpicTimelineId } from './my_epic_timeline_id';
import { dispatcherTimelinePersistQueue } from './epic_dispatcher_timeline_persistence_queue';
import type { ActionTimeline, TimelineById } from './types';
import { persistNote } from '../../containers/notes/api';
import type { ResponseNote } from '../../../../common/types/timeline/note';
import type { ResponseNote } from '../../../../common/types/timeline/note/api';
export const timelineNoteActionsType = [addNote.type, addNoteToEvent.type];

View file

@ -14,7 +14,7 @@ import { filter, mergeMap, startWith, withLatestFrom, takeUntil } from 'rxjs/ope
import { addError } from '../../../common/store/app/actions';
import type { inputsModel } from '../../../common/store/inputs';
import type { PinnedEvent } from '../../../../common/types/timeline/pinned_event';
import type { PinnedEventResponse } from '../../../../common/types/timeline/pinned_event/api';
import {
pinEvent,
endTimelineSaving,
@ -51,7 +51,7 @@ export const epicPersistPinnedEvent = (
withLatestFrom(timeline$, allTimelineQuery$),
mergeMap(([result, recentTimeline, allTimelineQuery]) => {
const savedTimeline = recentTimeline[action.payload.id];
const response: PinnedEvent = get('data.persistPinnedEventOnTimeline', result);
const response: PinnedEventResponse = get('data.persistPinnedEventOnTimeline', result);
const callOutMsg = response && response.code === 403 ? [showCallOutUnauthorizedMsg()] : [];
if (allTimelineQuery.refetch != null) {

View file

@ -26,7 +26,7 @@ import type {
TimelineStatus,
TimelineType,
} from '../../../../common/types/timeline/api';
import type { PinnedEvent } from '../../../../common/types/timeline/pinned_event';
import type { PinnedEvent } from '../../../../common/types/timeline/pinned_event/api';
import type { ResolveTimelineConfig } from '../../components/open_timeline/types';
export type KqlMode = 'filter' | 'search';

View file

@ -10,6 +10,7 @@ import { savePinnedEvents } from '../../../saved_object/pinned_events';
import { getNote } from '../../../saved_object/notes';
import type { FrameworkRequest } from '../../../../framework';
import type { SavedTimeline } from '../../../../../../common/types/timeline/api';
import type { Note } from '../../../../../../common/types/timeline/note/api';
import { mockTemplate, mockTimeline } from '../../../__mocks__/create_timelines';
import { buildFrameworkRequest } from '../../../utils/common';
import type { SecurityPluginSetup } from '@kbn/security-plugin/server';
@ -26,8 +27,15 @@ const timeline = { ...mockTimeline } as SavedTimeline;
const timelineSavedObjectId = null;
const timelineVersion = null;
const pinnedEventIds = ['123'];
const notes = [
{ noteId: 'abc', note: 'new note', timelineId: '', created: 1603885051655, createdBy: 'elastic' },
const notes: Note[] = [
{
noteId: 'abc',
note: 'new note',
timelineId: '',
created: 1603885051655,
createdBy: 'elastic',
version: 'testVersion',
},
];
const existingNoteIds = undefined;
const isImmutable = true;
@ -119,6 +127,7 @@ describe('createTimelines', () => {
note: 'new note',
noteId: 'abc',
timelineId: '',
version: 'testVersion',
},
]);
});

View file

@ -12,7 +12,7 @@ import { timeline as timelineLib, pinnedEvent as pinnedEventLib } from '../../..
import type { FrameworkRequest } from '../../../../framework';
import type { ResponseTimeline, SavedTimeline } from '../../../../../../common/types/timeline/api';
import { persistNotes } from '../../../saved_object/notes/persist_notes';
import type { NoteResult } from '../../../../../../common/types/timeline/note';
import type { Note } from '../../../../../../common/types/timeline/note/api';
interface CreateTimelineProps {
frameworkRequest: FrameworkRequest;
@ -21,7 +21,7 @@ interface CreateTimelineProps {
timelineVersion?: string | null;
overrideNotesOwner?: boolean;
pinnedEventIds?: string[] | null;
notes?: NoteResult[];
notes?: Note[];
existingNoteIds?: string[];
isImmutable?: boolean;
}

View file

@ -13,8 +13,8 @@ import type {
ExportedNotes,
ExportTimelineNotFoundError,
} from '../../../../../../common/types/timeline/api';
import type { NoteSavedObject } from '../../../../../../common/types/timeline/note';
import type { PinnedEventSavedObject } from '../../../../../../common/types/timeline/pinned_event';
import type { Note } from '../../../../../../common/types/timeline/note/api';
import type { PinnedEvent } from '../../../../../../common/types/timeline/pinned_event/api';
import type { FrameworkRequest } from '../../../../framework';
import * as noteLib from '../../../saved_object/notes';
@ -22,7 +22,7 @@ import * as pinnedEventLib from '../../../saved_object/pinned_events';
import { getSelectedTimelines } from '../../../saved_object/timelines';
const getGlobalEventNotesByTimelineId = (currentNotes: NoteSavedObject[]): ExportedNotes => {
const getGlobalEventNotesByTimelineId = (currentNotes: Note[]): ExportedNotes => {
const initialNotes: ExportedNotes = {
eventNotes: [],
globalNotes: [],
@ -38,9 +38,7 @@ const getGlobalEventNotesByTimelineId = (currentNotes: NoteSavedObject[]): Expor
}, initialNotes);
};
const getPinnedEventsIdsByTimelineId = (
currentPinnedEvents: PinnedEventSavedObject[]
): string[] => {
const getPinnedEventsIdsByTimelineId = (currentPinnedEvents: PinnedEvent[]): string[] => {
return currentPinnedEvents.map((event) => event.eventId) ?? [];
};
@ -60,9 +58,9 @@ const getTimelinesFromObjects = async (
),
]);
const myNotes = notes.reduce<NoteSavedObject[]>((acc, note) => [...acc, ...note], []);
const myNotes = notes.reduce<Note[]>((acc, note) => [...acc, ...note], []);
const myPinnedEventIds = pinnedEvents.reduce<PinnedEventSavedObject[]>(
const myPinnedEventIds = pinnedEvents.reduce<PinnedEvent[]>(
(acc, pinnedEventId) => [...acc, ...pinnedEventId],
[]
);

View file

@ -268,28 +268,34 @@ describe('import timelines', () => {
expect(mockPersistNote.mock.calls[0][0].note).toEqual({
eventId: undefined,
note: 'original note',
noteId: 'd2649d40-6bc5-11ea-86f0-5db0048c6086',
created: '1584830796960',
createdBy: 'original author A',
updated: '1584830796960',
updatedBy: 'original author A',
version: 'WzExNjQsMV0=',
timelineId: mockCreatedTimeline.savedObjectId,
});
expect(mockPersistNote.mock.calls[1][0].note).toEqual({
eventId: mockUniqueParsedObjects[0].eventNotes[0].eventId,
note: 'original event note',
noteId: '73ac2370-6bc2-11ea-a90b-f5341fb7a189',
created: '1584830796960',
createdBy: 'original author B',
updated: '1584830796960',
updatedBy: 'original author B',
version: 'WzExMjgsMV0=',
timelineId: mockCreatedTimeline.savedObjectId,
});
expect(mockPersistNote.mock.calls[2][0].note).toEqual({
eventId: mockUniqueParsedObjects[0].eventNotes[1].eventId,
note: 'event note2',
noteId: 'f7b71620-6bc2-11ea-a0b6-33c7b2a78885',
created: '1584830796960',
createdBy: 'angela',
updated: '1584830796960',
updatedBy: 'angela',
version: 'WzExMzUsMV0=',
timelineId: mockCreatedTimeline.savedObjectId,
});
});
@ -307,8 +313,11 @@ describe('import timelines', () => {
updatedBy: mockUniqueParsedObjects[0].globalNotes[0].updatedBy,
eventId: undefined,
note: mockUniqueParsedObjects[0].globalNotes[0].note,
noteId: mockUniqueParsedObjects[0].globalNotes[0].noteId,
timelineId: mockCreatedTimeline.savedObjectId,
version: mockUniqueParsedObjects[0].globalNotes[0].version,
});
expect(mockPersistNote.mock.calls[1][0].note).toEqual({
created: mockUniqueParsedObjects[0].eventNotes[0].created,
createdBy: mockUniqueParsedObjects[0].eventNotes[0].createdBy,
@ -316,7 +325,9 @@ describe('import timelines', () => {
updatedBy: mockUniqueParsedObjects[0].eventNotes[0].updatedBy,
eventId: mockUniqueParsedObjects[0].eventNotes[0].eventId,
note: mockUniqueParsedObjects[0].eventNotes[0].note,
noteId: mockUniqueParsedObjects[0].eventNotes[0].noteId,
timelineId: mockCreatedTimeline.savedObjectId,
version: mockUniqueParsedObjects[0].eventNotes[0].version,
});
expect(mockPersistNote.mock.calls[2][0].note).toEqual({
created: mockUniqueParsedObjects[0].eventNotes[1].created,
@ -325,7 +336,9 @@ describe('import timelines', () => {
updatedBy: mockUniqueParsedObjects[0].eventNotes[1].updatedBy,
eventId: mockUniqueParsedObjects[0].eventNotes[1].eventId,
note: mockUniqueParsedObjects[0].eventNotes[1].note,
noteId: mockUniqueParsedObjects[0].eventNotes[1].noteId,
timelineId: mockCreatedTimeline.savedObjectId,
version: mockUniqueParsedObjects[0].eventNotes[1].version,
});
});
@ -641,10 +654,12 @@ describe('import timeline templates', () => {
expect(mockPersistNote.mock.calls[0][0].note).toEqual({
eventId: undefined,
note: mockUniqueParsedTemplateTimelineObjects[0].globalNotes[0].note,
noteId: mockUniqueParsedTemplateTimelineObjects[0].globalNotes[0].noteId,
created: mockUniqueParsedTemplateTimelineObjects[0].globalNotes[0].created,
createdBy: mockUniqueParsedTemplateTimelineObjects[0].globalNotes[0].createdBy,
updated: mockUniqueParsedTemplateTimelineObjects[0].globalNotes[0].updated,
updatedBy: mockUniqueParsedTemplateTimelineObjects[0].globalNotes[0].updatedBy,
version: mockUniqueParsedTemplateTimelineObjects[0].globalNotes[0].version,
timelineId: mockCreatedTemplateTimeline.savedObjectId,
});
});
@ -787,10 +802,12 @@ describe('import timeline templates', () => {
expect(mockPersistNote.mock.calls[0][0].note).toEqual({
eventId: undefined,
note: mockUniqueParsedTemplateTimelineObjects[0].globalNotes[0].note,
noteId: mockUniqueParsedTemplateTimelineObjects[0].globalNotes[0].noteId,
created: mockUniqueParsedTemplateTimelineObjects[0].globalNotes[0].created,
createdBy: mockUniqueParsedTemplateTimelineObjects[0].globalNotes[0].createdBy,
updated: mockUniqueParsedTemplateTimelineObjects[0].globalNotes[0].updated,
updatedBy: mockUniqueParsedTemplateTimelineObjects[0].globalNotes[0].updatedBy,
version: mockUniqueParsedTemplateTimelineObjects[0].globalNotes[0].version,
timelineId: mockCreatedTemplateTimeline.savedObjectId,
});
});

View file

@ -8,14 +8,14 @@ import type { BulkError } from '../../../../detection_engine/routes/utils';
import type { SavedTimeline } from '../../../../../../common/types/timeline/api';
import type { TimelineStatusActions } from '../../../utils/common';
import type { NoteResult } from '../../../../../../common/types/timeline/note';
import type { Note } from '../../../../../../common/types/timeline/note/api';
export type ImportedTimeline = SavedTimeline & {
savedObjectId: string | null;
version: string | null;
pinnedEventIds: string[];
globalNotes: NoteResult[];
eventNotes: NoteResult[];
globalNotes: Note[];
eventNotes: Note[];
};
export type PromiseFromStreams = ImportedTimeline;

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import type { NoteResult, SavedNote } from '../../../../../common/types/timeline/note';
import type { Note } from '../../../../../common/types/timeline/note/api';
import type { FrameworkRequest } from '../../../framework';
import { getNote } from './saved_object';
@ -16,10 +16,10 @@ import { getNote } from './saved_object';
export const getOverridableNote = async (
frameworkRequest: FrameworkRequest,
note: NoteResult,
note: Note,
timelineSavedObjectId: string,
overrideOwner: boolean
): Promise<SavedNote> => {
): Promise<Note> => {
let savedNote = note;
try {
savedNote = await getNote(frameworkRequest, note.noteId);
@ -30,6 +30,8 @@ export const getOverridableNote = async (
eventId: note.eventId,
note: note.note,
timelineId: timelineSavedObjectId,
version: note.version,
noteId: note.noteId,
}
: {
eventId: savedNote.eventId,
@ -39,5 +41,7 @@ export const getOverridableNote = async (
updated: savedNote.updated,
updatedBy: savedNote.updatedBy,
timelineId: timelineSavedObjectId,
version: note.version,
noteId: note.noteId,
};
};

View file

@ -8,13 +8,13 @@
import type { FrameworkRequest } from '../../../framework';
import { persistNote } from './saved_object';
import { getOverridableNote } from './get_overridable_note';
import type { NoteResult } from '../../../../../common/types/timeline/note';
import type { Note } from '../../../../../common/types/timeline/note/api';
export const persistNotes = async (
frameworkRequest: FrameworkRequest,
timelineSavedObjectId: string,
existingNoteIds?: string[],
newNotes?: NoteResult[],
newNotes?: Note[],
overrideOwner: boolean = true
) => {
return Promise.all(

View file

@ -6,13 +6,13 @@
*/
import type { AuthenticatedUser } from '@kbn/security-plugin/common/model';
import type { NoteSavedObject } from '../../../../../common/types/timeline/note';
import type { Note } from '../../../../../common/types/timeline/note/api';
import { pickSavedNote } from './saved_object';
describe('saved_object', () => {
const mockDateNow = new Date('2020-04-03T23:00:00.000Z').valueOf();
const getMockSavedNote = (): NoteSavedObject => ({
const getMockSavedNote = (): Note => ({
noteId: '7ba7a520-03f4-11eb-9d9d-ffba20fabba8',
version: 'WzQ0ODEsMV0=',
note: '789',

View file

@ -18,13 +18,13 @@ import type { AuthenticatedUser } from '@kbn/security-plugin/common/model';
import { getUserDisplayName } from '@kbn/user-profile-components';
import { UNAUTHENTICATED_USER } from '../../../../../common/constants';
import type {
SavedNote,
NoteSavedObject,
NoteResult,
Note,
BareNote,
BareNoteWithoutExternalRefs,
ResponseNote,
NoteWithoutExternalRefs,
} from '../../../../../common/types/timeline/note';
import { NoteSavedObjectRuntimeType } from '../../../../../common/types/timeline/note';
} from '../../../../../common/types/timeline/note/api';
import { SavedObjectNoteRuntimeType } from '../../../../../common/types/timeline/note/saved_object';
import type { SavedObjectNoteWithoutExternalRefs } from '../../../../../common/types/timeline/note/saved_object';
import type { FrameworkRequest } from '../../../framework';
import { noteSavedObjectType } from '../../saved_object_mappings/notes';
import { createTimeline } from '../timelines';
@ -60,17 +60,14 @@ export const deleteNote = async ({
await savedObjectsClient.delete(noteSavedObjectType, noteId);
};
export const getNote = async (
request: FrameworkRequest,
noteId: string
): Promise<NoteSavedObject> => {
export const getNote = async (request: FrameworkRequest, noteId: string): Promise<Note> => {
return getSavedNote(request, noteId);
};
export const getNotesByTimelineId = async (
request: FrameworkRequest,
timelineId: string
): Promise<NoteSavedObject[]> => {
): Promise<Note[]> => {
const options: SavedObjectsFindOptions = {
type: noteSavedObjectType,
hasReference: { type: timelineSavedObjectType, id: timelineId },
@ -87,7 +84,7 @@ export const persistNote = async ({
}: {
request: FrameworkRequest;
noteId: string | null;
note: SavedNote;
note: BareNote;
overrideOwner?: boolean;
}): Promise<ResponseNote> => {
try {
@ -104,7 +101,7 @@ export const persistNote = async ({
return await updateNote({ request, noteId, note, overrideOwner });
} catch (err) {
if (getOr(null, 'output.statusCode', err) === 403) {
const noteToReturn: NoteResult = {
const noteToReturn: Note = {
...note,
noteId: uuidv1(),
version: '',
@ -129,7 +126,7 @@ const createNote = async ({
}: {
request: FrameworkRequest;
noteId: string | null;
note: SavedNote;
note: BareNote;
overrideOwner?: boolean;
}) => {
const savedObjectsClient = (await request.context.core).savedObjects.client;
@ -155,13 +152,22 @@ const createNote = async ({
: shallowCopyOfNote;
const { transformedFields: migratedAttributes, references } =
noteFieldsMigrator.extractFieldsToReferences<NoteWithoutExternalRefs>({
noteFieldsMigrator.extractFieldsToReferences<BareNoteWithoutExternalRefs>({
data: noteWithCreator,
});
const createdNote = await savedObjectsClient.create<NoteWithoutExternalRefs>(
const noteAttributes: SavedObjectNoteWithoutExternalRefs = {
eventId: migratedAttributes.eventId,
note: migratedAttributes.note,
created: migratedAttributes.created,
createdBy: migratedAttributes.createdBy,
updated: migratedAttributes.updated,
updatedBy: migratedAttributes.updatedBy,
};
const createdNote = await savedObjectsClient.create<SavedObjectNoteWithoutExternalRefs>(
noteSavedObjectType,
migratedAttributes,
noteAttributes,
{
references,
}
@ -187,13 +193,13 @@ const updateNote = async ({
}: {
request: FrameworkRequest;
noteId: string;
note: SavedNote;
note: BareNote;
overrideOwner?: boolean;
}) => {
const savedObjectsClient = (await request.context.core).savedObjects.client;
const userInfo = request.user;
const existingNote = await savedObjectsClient.get<NoteWithoutExternalRefs>(
const existingNote = await savedObjectsClient.get<SavedObjectNoteWithoutExternalRefs>(
noteSavedObjectType,
noteId
);
@ -201,20 +207,24 @@ const updateNote = async ({
const noteWithCreator = overrideOwner ? pickSavedNote(noteId, note, userInfo) : note;
const { transformedFields: migratedPatchAttributes, references } =
noteFieldsMigrator.extractFieldsToReferences<NoteWithoutExternalRefs>({
noteFieldsMigrator.extractFieldsToReferences<BareNoteWithoutExternalRefs>({
data: noteWithCreator,
existingReferences: existingNote.references,
});
const updatedNote = await savedObjectsClient.update(
noteSavedObjectType,
noteId,
migratedPatchAttributes,
{
version: existingNote.version || undefined,
references,
}
);
const noteAttributes: SavedObjectNoteWithoutExternalRefs = {
eventId: migratedPatchAttributes.eventId,
note: migratedPatchAttributes.note,
created: migratedPatchAttributes.created,
createdBy: migratedPatchAttributes.createdBy,
updated: migratedPatchAttributes.updated,
updatedBy: migratedPatchAttributes.updatedBy,
};
const updatedNote = await savedObjectsClient.update(noteSavedObjectType, noteId, noteAttributes, {
version: existingNote.version || undefined,
references,
});
const populatedNote = noteFieldsMigrator.populateFieldsFromReferencesForPatch({
dataBeforeRequest: note,
@ -232,7 +242,7 @@ const updateNote = async ({
const getSavedNote = async (request: FrameworkRequest, NoteId: string) => {
const savedObjectsClient = (await request.context.core).savedObjects.client;
const savedObject = await savedObjectsClient.get<NoteWithoutExternalRefs>(
const savedObject = await savedObjectsClient.get<SavedObjectNoteWithoutExternalRefs>(
noteSavedObjectType,
NoteId
);
@ -244,7 +254,7 @@ const getSavedNote = async (request: FrameworkRequest, NoteId: string) => {
const getAllSavedNote = async (request: FrameworkRequest, options: SavedObjectsFindOptions) => {
const savedObjectsClient = (await request.context.core).savedObjects.client;
const savedObjects = await savedObjectsClient.find<NoteWithoutExternalRefs>(options);
const savedObjects = await savedObjectsClient.find<SavedObjectNoteWithoutExternalRefs>(options);
return {
totalCount: savedObjects.total,
@ -259,15 +269,23 @@ const getAllSavedNote = async (request: FrameworkRequest, options: SavedObjectsF
export const convertSavedObjectToSavedNote = (
savedObject: unknown,
timelineVersion?: string | undefined | null
): NoteSavedObject =>
): Note =>
pipe(
NoteSavedObjectRuntimeType.decode(savedObject),
map((savedNote) => ({
noteId: savedNote.id,
version: savedNote.version,
timelineVersion,
...savedNote.attributes,
})),
SavedObjectNoteRuntimeType.decode(savedObject),
map((savedNote) => {
return {
noteId: savedNote.id,
version: savedNote.version,
timelineVersion,
timelineId: savedNote.attributes.timelineId,
eventId: savedNote.attributes.eventId,
note: savedNote.attributes.note,
created: savedNote.attributes.created,
createdBy: savedNote.attributes.createdBy,
updated: savedNote.attributes.updated,
updatedBy: savedNote.attributes.updatedBy,
};
}),
fold((errors) => {
throw new Error(failure(errors).join('\n'));
}, identity)
@ -275,7 +293,7 @@ export const convertSavedObjectToSavedNote = (
export const pickSavedNote = (
noteId: string | null,
savedNote: SavedNote,
savedNote: BareNote,
userInfo: AuthenticatedUser | null
) => {
if (noteId == null) {

View file

@ -15,12 +15,13 @@ import type { SavedObjectsFindOptions } from '@kbn/core/server';
import type { AuthenticatedUser } from '@kbn/security-plugin/common/model';
import { UNAUTHENTICATED_USER } from '../../../../../common/constants';
import type {
PinnedEventSavedObject,
SavedPinnedEvent,
PinnedEvent as PinnedEventResponse,
PinnedEventWithoutExternalRefs,
} from '../../../../../common/types/timeline/pinned_event';
import { PinnedEventSavedObjectRuntimeType } from '../../../../../common/types/timeline/pinned_event';
BarePinnedEvent,
PinnedEvent,
PinnedEventResponse,
BarePinnedEventWithoutExternalRefs,
} from '../../../../../common/types/timeline/pinned_event/api';
import { SavedObjectPinnedEventRuntimeType } from '../../../../../common/types/timeline/pinned_event/saved_object';
import type { SavedObjectPinnedEventWithoutExternalRefs } from '../../../../../common/types/timeline/pinned_event/saved_object';
import type { FrameworkRequest } from '../../../framework';
import { createTimeline } from '../timelines';
@ -64,7 +65,7 @@ export const PINNED_EVENTS_PER_PAGE = 10000; // overrides the saved object clien
export const getAllPinnedEventsByTimelineId = async (
request: FrameworkRequest,
timelineId: string
): Promise<PinnedEventSavedObject[]> => {
): Promise<PinnedEvent[]> => {
const options: SavedObjectsFindOptions = {
type: pinnedEventSavedObjectType,
hasReference: { type: timelineSavedObjectType, id: timelineId },
@ -99,7 +100,7 @@ export const persistPinnedEventOnTimeline = async (
// we already had this event pinned so let's just return the one we already had
if (pinnedEvents.length > 0) {
return pinnedEvents[0];
return { ...pinnedEvents[0], code: 200 };
}
return await createPinnedEvent({
@ -124,6 +125,8 @@ export const persistPinnedEventOnTimeline = async (
pinnedEventId: eventId,
timelineId: '',
timelineVersion: '',
version: '',
eventId: '',
}
: null;
}
@ -161,7 +164,7 @@ const getPinnedEventsInTimelineWithEventId = async (
request: FrameworkRequest,
timelineId: string,
eventId: string
): Promise<PinnedEventSavedObject[]> => {
): Promise<PinnedEvent[]> => {
const allPinnedEventId = await getAllPinnedEventsByTimelineId(request, timelineId);
const pinnedEvents = allPinnedEventId.filter((pinnedEvent) => pinnedEvent.eventId === eventId);
@ -178,10 +181,10 @@ const createPinnedEvent = async ({
eventId: string;
timelineId: string;
timelineVersion?: string;
}) => {
}): Promise<PinnedEventResponse> => {
const savedObjectsClient = (await request.context.core).savedObjects.client;
const savedPinnedEvent: SavedPinnedEvent = {
const savedPinnedEvent: BarePinnedEvent = {
eventId,
timelineId,
};
@ -189,21 +192,33 @@ const createPinnedEvent = async ({
const pinnedEventWithCreator = pickSavedPinnedEvent(null, savedPinnedEvent, request.user);
const { transformedFields: migratedAttributes, references } =
pinnedEventFieldsMigrator.extractFieldsToReferences<PinnedEventWithoutExternalRefs>({
pinnedEventFieldsMigrator.extractFieldsToReferences<BarePinnedEventWithoutExternalRefs>({
data: pinnedEventWithCreator,
});
const createdPinnedEvent = await savedObjectsClient.create<PinnedEventWithoutExternalRefs>(
pinnedEventSavedObjectType,
migratedAttributes,
{ references }
);
const pinnedEventAttributes: SavedObjectPinnedEventWithoutExternalRefs = {
eventId: migratedAttributes.eventId,
created: migratedAttributes.created,
createdBy: migratedAttributes.createdBy,
updated: migratedAttributes.updated,
updatedBy: migratedAttributes.updatedBy,
};
const createdPinnedEvent =
await savedObjectsClient.create<SavedObjectPinnedEventWithoutExternalRefs>(
pinnedEventSavedObjectType,
pinnedEventAttributes,
{ references }
);
const repopulatedSavedObject =
pinnedEventFieldsMigrator.populateFieldsFromReferences(createdPinnedEvent);
// create Pinned Event on Timeline
return convertSavedObjectToSavedPinnedEvent(repopulatedSavedObject, timelineVersion);
return {
...convertSavedObjectToSavedPinnedEvent(repopulatedSavedObject, timelineVersion),
code: 200,
};
};
const getAllSavedPinnedEvents = async (
@ -211,7 +226,9 @@ const getAllSavedPinnedEvents = async (
options: SavedObjectsFindOptions
) => {
const savedObjectsClient = (await request.context.core).savedObjects.client;
const savedObjects = await savedObjectsClient.find<PinnedEventWithoutExternalRefs>(options);
const savedObjects = await savedObjectsClient.find<SavedObjectPinnedEventWithoutExternalRefs>(
options
);
return savedObjects.saved_objects.map((savedObject) => {
const populatedPinnedEvent =
@ -240,15 +257,22 @@ export const savePinnedEvents = (
export const convertSavedObjectToSavedPinnedEvent = (
savedObject: unknown,
timelineVersion?: string | undefined | null
): PinnedEventSavedObject =>
): PinnedEvent =>
pipe(
PinnedEventSavedObjectRuntimeType.decode(savedObject),
map((savedPinnedEvent) => ({
pinnedEventId: savedPinnedEvent.id,
version: savedPinnedEvent.version,
timelineVersion,
...savedPinnedEvent.attributes,
})),
SavedObjectPinnedEventRuntimeType.decode(savedObject),
map((savedPinnedEvent) => {
return {
pinnedEventId: savedPinnedEvent.id,
version: savedPinnedEvent.version,
timelineVersion,
timelineId: savedPinnedEvent.attributes.timelineId,
created: savedPinnedEvent.attributes.created,
createdBy: savedPinnedEvent.attributes.createdBy,
eventId: savedPinnedEvent.attributes.eventId,
updated: savedPinnedEvent.attributes.updated,
updatedBy: savedPinnedEvent.attributes.updatedBy,
};
}),
fold((errors) => {
throw new Error(failure(errors).join('\n'));
}, identity)
@ -256,7 +280,7 @@ export const convertSavedObjectToSavedPinnedEvent = (
export const pickSavedPinnedEvent = (
pinnedEventId: string | null,
savedPinnedEvent: SavedPinnedEvent,
savedPinnedEvent: BarePinnedEvent,
userInfo: AuthenticatedUser | null
) => {
const dateNow = new Date().valueOf();

View file

@ -15,8 +15,8 @@ import {
import type { AuthenticatedUser } from '@kbn/security-plugin/server';
import { UNAUTHENTICATED_USER } from '../../../../../common/constants';
import type { NoteSavedObject } from '../../../../../common/types/timeline/note';
import type { PinnedEventSavedObject } from '../../../../../common/types/timeline/pinned_event';
import type { Note } from '../../../../../common/types/timeline/note/api';
import type { PinnedEvent } from '../../../../../common/types/timeline/pinned_event/api';
import type {
AllTimelinesResponse,
ExportTimelineNotFoundError,
@ -684,8 +684,8 @@ const getAllSavedTimeline = async (request: FrameworkRequest, options: SavedObje
export const convertStringToBase64 = (text: string): string => Buffer.from(text).toString('base64');
export const timelineWithReduxProperties = (
notes: NoteSavedObject[],
pinnedEvents: PinnedEventSavedObject[],
notes: Note[],
pinnedEvents: PinnedEvent[],
timeline: TimelineSavedObject,
userName: string
): TimelineSavedObject => ({

View file

@ -9,7 +9,7 @@ import type { AuthenticatedUser } from '@kbn/security-plugin/common/model';
import type { SavedTimeline } from '../../../../../common/types/timeline/api';
import { TimelineStatus, TimelineType } from '../../../../../common/types/timeline/api';
import type { NoteSavedObject } from '../../../../../common/types/timeline/note';
import type { Note } from '../../../../../common/types/timeline/note/api';
import { pickSavedTimeline } from './pick_saved_timeline';
@ -18,8 +18,8 @@ describe('pickSavedTimeline', () => {
const getMockSavedTimeline = (): SavedTimeline & {
savedObjectId?: string | null;
version?: string;
eventNotes?: NoteSavedObject[];
globalNotes?: NoteSavedObject[];
eventNotes?: Note[];
globalNotes?: Note[];
pinnedEventIds?: [];
} => ({
savedObjectId: '7af80430-03f4-11eb-9d9d-ffba20fabba8',

View file

@ -7,14 +7,14 @@
import * as runtimeTypes from 'io-ts';
import { unionWithNullType } from '../../../../../common/utility_types';
import { NoteServerRepresentation } from '../../../../../common/types/timeline/note';
import { BareNoteSchema } from '../../../../../common/types/timeline/note/api';
export const eventNotes = unionWithNullType(runtimeTypes.array(NoteServerRepresentation));
export const globalNotes = unionWithNullType(runtimeTypes.array(NoteServerRepresentation));
export const eventNotes = unionWithNullType(runtimeTypes.array(BareNoteSchema));
export const globalNotes = unionWithNullType(runtimeTypes.array(BareNoteSchema));
export const persistNoteSchema = runtimeTypes.intersection([
runtimeTypes.type({
note: NoteServerRepresentation,
note: BareNoteSchema,
}),
runtimeTypes.partial({
overrideOwner: unionWithNullType(runtimeTypes.boolean),

View file

@ -12,9 +12,9 @@ import {
timelineSavedObjectType,
} from '@kbn/security-solution-plugin/server/lib/timeline/saved_object_mappings';
import { TimelineWithoutExternalRefs } from '@kbn/security-solution-plugin/common/types/timeline/api';
import { NoteWithoutExternalRefs } from '@kbn/security-solution-plugin/common/types/timeline/note';
import { BareNoteWithoutExternalRefs } from '@kbn/security-solution-plugin/common/types/timeline/note/api';
import { PinnedEventWithoutExternalRefs } from '@kbn/security-solution-plugin/common/types/timeline/pinned_event';
import { BarePinnedEventWithoutExternalRefs } from '@kbn/security-solution-plugin/common/types/timeline/pinned_event/api';
import { FtrProviderContext } from '../../ftr_provider_context';
import { getSavedObjectFromES } from './utils';
@ -23,11 +23,11 @@ interface TimelineWithoutSavedQueryId {
}
interface NoteWithoutTimelineId {
[noteSavedObjectType]: NoteWithoutExternalRefs;
[noteSavedObjectType]: BareNoteWithoutExternalRefs;
}
interface PinnedEventWithoutTimelineId {
[pinnedEventSavedObjectType]: PinnedEventWithoutExternalRefs;
[pinnedEventSavedObjectType]: BarePinnedEventWithoutExternalRefs;
}
export default function ({ getService }: FtrProviderContext) {