diff --git a/x-pack/plugins/security_solution/common/types/timeline/index.ts b/x-pack/plugins/security_solution/common/types/timeline/index.ts index e67eb3182ffa99..4f255bb6d68341 100644 --- a/x-pack/plugins/security_solution/common/types/timeline/index.ts +++ b/x-pack/plugins/security_solution/common/types/timeline/index.ts @@ -197,6 +197,32 @@ export interface SavedTimeline extends runtimeTypes.TypeOf {} +/* + * Timeline IDs + */ + +export enum TimelineId { + hostsPageEvents = 'hosts-page-events', + hostsPageExternalAlerts = 'hosts-page-external-alerts', + alertsRulesDetailsPage = 'alerts-rules-details-page', + alertsPage = 'alerts-page', + networkPageExternalAlerts = 'network-page-external-alerts', + active = 'timeline-1', + test = 'test', // Reserved for testing purposes +} + +export const TimelineIdLiteralRt = runtimeTypes.union([ + runtimeTypes.literal(TimelineId.hostsPageEvents), + runtimeTypes.literal(TimelineId.hostsPageExternalAlerts), + runtimeTypes.literal(TimelineId.alertsRulesDetailsPage), + runtimeTypes.literal(TimelineId.alertsPage), + runtimeTypes.literal(TimelineId.networkPageExternalAlerts), + runtimeTypes.literal(TimelineId.active), + runtimeTypes.literal(TimelineId.test), +]); + +export type TimelineIdLiteral = runtimeTypes.TypeOf; + /** * Timeline Saved object type with metadata */ diff --git a/x-pack/plugins/security_solution/public/alerts/components/alerts_table/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/index.test.tsx index 407cbcf5f9fac3..f843bf68818465 100644 --- a/x-pack/plugins/security_solution/public/alerts/components/alerts_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/index.test.tsx @@ -7,13 +7,14 @@ import React from 'react'; import { shallow } from 'enzyme'; +import { TimelineId } from '../../../../common/types/timeline'; import { AlertsTableComponent } from './index'; describe('AlertsTableComponent', () => { it('renders correctly', () => { const wrapper = shallow( = ({ filters, @@ -139,7 +139,7 @@ export const DetectionEnginePageComponent: React.FC = ({ /> = ({ {ruleId != null && ( { - timelineId: TimelineId; + timelineId: TimelineIdLiteral; pageFilters: Filter[]; stackByOptions?: MatrixHistogramOption[]; defaultFilters?: Filter[]; diff --git a/x-pack/plugins/security_solution/public/hosts/constants.ts b/x-pack/plugins/security_solution/public/hosts/constants.ts deleted file mode 100644 index e48c00b1d72698..00000000000000 --- a/x-pack/plugins/security_solution/public/hosts/constants.ts +++ /dev/null @@ -1,8 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -export const EVENTS_TIMELINE_ID = 'hosts-page-events'; -export const EXTERNAL_ALERTS_TIMELINE_ID = 'hosts-page-external-alerts'; diff --git a/x-pack/plugins/security_solution/public/hosts/index.ts b/x-pack/plugins/security_solution/public/hosts/index.ts index 716442d1f365a3..90d5f54a027d7f 100644 --- a/x-pack/plugins/security_solution/public/hosts/index.ts +++ b/x-pack/plugins/security_solution/public/hosts/index.ts @@ -5,14 +5,16 @@ */ import { Storage } from '../../../../../src/plugins/kibana_utils/public'; +import { TimelineIdLiteral, TimelineId } from '../../common/types/timeline'; import { SecuritySubPluginWithStore } from '../app/types'; import { getTimelinesInStorageByIds } from '../timelines/containers/local_storage'; -import { TimelineId } from '../timelines/containers/local_storage/types'; import { getHostsRoutes } from './routes'; import { initialHostsState, hostsReducer, HostsState } from './store'; -import { EVENTS_TIMELINE_ID, EXTERNAL_ALERTS_TIMELINE_ID } from './constants'; -const HOST_TIMELINE_IDS: TimelineId[] = [EVENTS_TIMELINE_ID, EXTERNAL_ALERTS_TIMELINE_ID]; +const HOST_TIMELINE_IDS: TimelineIdLiteral[] = [ + TimelineId.hostsPageEvents, + TimelineId.hostsPageExternalAlerts, +]; export class Hosts { public setup() {} diff --git a/x-pack/plugins/security_solution/public/hosts/pages/navigation/alerts_query_tab_body.tsx b/x-pack/plugins/security_solution/public/hosts/pages/navigation/alerts_query_tab_body.tsx index 7819e4fa62a2e2..3023670f8051a0 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/navigation/alerts_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/navigation/alerts_query_tab_body.tsx @@ -7,11 +7,10 @@ import React, { useMemo } from 'react'; import { Filter } from '../../../../../../../src/plugins/data/public'; +import { TimelineId } from '../../../../common/types/timeline'; import { AlertsView } from '../../../common/components/alerts_viewer'; import { AlertsComponentQueryProps } from './types'; -const ALERTS_TABLE_ID = 'hosts-page-external-alerts'; - export const filterHostData: Filter[] = [ { query: { @@ -50,7 +49,13 @@ export const HostAlertsQueryTabBody = React.memo((alertsProps: AlertsComponentQu [pageFilters] ); - return ; + return ( + + ); }); HostAlertsQueryTabBody.displayName = 'HostAlertsQueryTabBody'; diff --git a/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx b/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx index 25387386171181..574e2ec4ae250a 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx @@ -5,6 +5,7 @@ */ import React, { useEffect } from 'react'; +import { TimelineId } from '../../../../common/types/timeline'; import { StatefulEventsViewer } from '../../../common/components/events_viewer'; import { HostsComponentsQueryProps } from './types'; import { hostsModel } from '../../store'; @@ -17,7 +18,6 @@ import { MatrixHistogramContainer } from '../../../common/components/matrix_hist import * as i18n from '../translations'; import { HistogramType } from '../../../graphql/types'; -const HOSTS_PAGE_TIMELINE_ID = 'hosts-page-events'; const EVENTS_HISTOGRAM_ID = 'eventsOverTimeQuery'; export const eventsStackByOptions: MatrixHistogramOption[] = [ @@ -78,7 +78,7 @@ export const EventsQueryTabBody = ({ diff --git a/x-pack/plugins/security_solution/public/network/constants.ts b/x-pack/plugins/security_solution/public/network/constants.ts deleted file mode 100644 index 23db79a6b48933..00000000000000 --- a/x-pack/plugins/security_solution/public/network/constants.ts +++ /dev/null @@ -1,7 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -export const NETWORK_PAGE_EXTERNAL_EVENTS_TIMELINE_ID = 'network-page-external-alerts'; diff --git a/x-pack/plugins/security_solution/public/network/index.ts b/x-pack/plugins/security_solution/public/network/index.ts index 3f628104e263df..63291ad2d2396a 100644 --- a/x-pack/plugins/security_solution/public/network/index.ts +++ b/x-pack/plugins/security_solution/public/network/index.ts @@ -8,8 +8,8 @@ import { Storage } from '../../../../../src/plugins/kibana_utils/public'; import { SecuritySubPluginWithStore } from '../app/types'; import { getNetworkRoutes } from './routes'; import { initialNetworkState, networkReducer, NetworkState } from './store'; +import { TimelineId } from '../../common/types/timeline'; import { getTimelinesInStorageByIds } from '../timelines/containers/local_storage'; -import { NETWORK_PAGE_EXTERNAL_EVENTS_TIMELINE_ID } from './constants'; export class Network { public setup() {} @@ -18,9 +18,7 @@ export class Network { return { routes: getNetworkRoutes(), storageTimelines: { - timelineById: getTimelinesInStorageByIds(storage, [ - NETWORK_PAGE_EXTERNAL_EVENTS_TIMELINE_ID, - ]), + timelineById: getTimelinesInStorageByIds(storage, [TimelineId.networkPageExternalAlerts]), }, store: { initialState: { network: initialNetworkState }, diff --git a/x-pack/plugins/security_solution/public/network/pages/navigation/alerts_query_tab_body.tsx b/x-pack/plugins/security_solution/public/network/pages/navigation/alerts_query_tab_body.tsx index c668f29160f3eb..0c9f8c194bf2b4 100644 --- a/x-pack/plugins/security_solution/public/network/pages/navigation/alerts_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/navigation/alerts_query_tab_body.tsx @@ -7,8 +7,8 @@ import React from 'react'; import { Filter } from '../../../../../../../src/plugins/data/common/es_query'; +import { TimelineId } from '../../../../common/types/timeline'; import { AlertsView } from '../../../common/components/alerts_viewer'; -import { NETWORK_PAGE_EXTERNAL_EVENTS_TIMELINE_ID } from '../../constants'; import { NetworkComponentQueryProps } from './types'; export const filterNetworkData: Filter[] = [ @@ -64,7 +64,7 @@ export const filterNetworkData: Filter[] = [ export const NetworkAlertsQueryTabBody = React.memo((alertsProps: NetworkComponentQueryProps) => ( diff --git a/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.test.ts b/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.test.ts index 54055c6e2ddb8f..e1bccbdff48899 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.test.ts @@ -11,6 +11,8 @@ import { getAllTimelinesInStorage, addTimelineInStorage, } from '.'; + +import { TimelineId } from '../../../../common/types/timeline'; import { mockTimelineModel, createSecuritySolutionStorageMock } from '../../../common/mock'; import { useKibana } from '../../../common/lib/kibana'; import { createUseKibanaMock } from '../../../common/mock/kibana_react'; @@ -36,19 +38,19 @@ describe('SiemLocalStorage', () => { describe('addTimeline', () => { it('adds a timeline when storage is empty', () => { const timelineStorage = useTimelinesStorage(); - timelineStorage.addTimeline('hosts-page-events', mockTimelineModel); + timelineStorage.addTimeline(TimelineId.hostsPageEvents, mockTimelineModel); expect(JSON.parse(localStorage.getItem(LOCAL_STORAGE_TIMELINE_KEY))).toEqual({ - 'hosts-page-events': mockTimelineModel, + [TimelineId.hostsPageEvents]: mockTimelineModel, }); }); it('adds a timeline when storage contains another timelines', () => { const timelineStorage = useTimelinesStorage(); - timelineStorage.addTimeline('hosts-page-events', mockTimelineModel); - timelineStorage.addTimeline('hosts-page-external-alerts', mockTimelineModel); + timelineStorage.addTimeline(TimelineId.hostsPageEvents, mockTimelineModel); + timelineStorage.addTimeline(TimelineId.hostsPageExternalAlerts, mockTimelineModel); expect(JSON.parse(localStorage.getItem(LOCAL_STORAGE_TIMELINE_KEY))).toEqual({ - 'hosts-page-events': mockTimelineModel, - 'hosts-page-external-alerts': mockTimelineModel, + [TimelineId.hostsPageEvents]: mockTimelineModel, + [TimelineId.hostsPageExternalAlerts]: mockTimelineModel, }); }); }); @@ -56,12 +58,12 @@ describe('SiemLocalStorage', () => { describe('getAllTimelines', () => { it('gets all timelines correctly', () => { const timelineStorage = useTimelinesStorage(); - timelineStorage.addTimeline('hosts-page-events', mockTimelineModel); - timelineStorage.addTimeline('hosts-page-external-alerts', mockTimelineModel); + timelineStorage.addTimeline(TimelineId.hostsPageEvents, mockTimelineModel); + timelineStorage.addTimeline(TimelineId.hostsPageExternalAlerts, mockTimelineModel); const timelines = timelineStorage.getAllTimelines(); expect(timelines).toEqual({ - 'hosts-page-events': mockTimelineModel, - 'hosts-page-external-alerts': mockTimelineModel, + [TimelineId.hostsPageEvents]: mockTimelineModel, + [TimelineId.hostsPageExternalAlerts]: mockTimelineModel, }); }); @@ -75,8 +77,8 @@ describe('SiemLocalStorage', () => { describe('getTimelineById', () => { it('gets a timeline by id', () => { const timelineStorage = useTimelinesStorage(); - timelineStorage.addTimeline('hosts-page-events', mockTimelineModel); - const timeline = timelineStorage.getTimelineById('hosts-page-events'); + timelineStorage.addTimeline(TimelineId.hostsPageEvents, mockTimelineModel); + const timeline = timelineStorage.getTimelineById(TimelineId.hostsPageEvents); expect(timeline).toEqual(mockTimelineModel); }); }); @@ -84,46 +86,46 @@ describe('SiemLocalStorage', () => { describe('getTimelinesInStorageByIds', () => { it('gets timelines correctly', () => { const timelineStorage = useTimelinesStorage(); - timelineStorage.addTimeline('hosts-page-events', mockTimelineModel); - timelineStorage.addTimeline('hosts-page-external-alerts', mockTimelineModel); + timelineStorage.addTimeline(TimelineId.hostsPageEvents, mockTimelineModel); + timelineStorage.addTimeline(TimelineId.hostsPageExternalAlerts, mockTimelineModel); const timelines = getTimelinesInStorageByIds(storage, [ - 'hosts-page-events', - 'hosts-page-external-alerts', + TimelineId.hostsPageEvents, + TimelineId.hostsPageExternalAlerts, ]); expect(timelines).toEqual({ - 'hosts-page-events': mockTimelineModel, - 'hosts-page-external-alerts': mockTimelineModel, + [TimelineId.hostsPageEvents]: mockTimelineModel, + [TimelineId.hostsPageExternalAlerts]: mockTimelineModel, }); }); it('gets an empty timelime when there is no timelines', () => { - const timelines = getTimelinesInStorageByIds(storage, ['hosts-page-events']); + const timelines = getTimelinesInStorageByIds(storage, [TimelineId.hostsPageEvents]); expect(timelines).toEqual({}); }); it('returns empty timelime when there is no ids', () => { const timelineStorage = useTimelinesStorage(); - timelineStorage.addTimeline('hosts-page-events', mockTimelineModel); + timelineStorage.addTimeline(TimelineId.hostsPageEvents, mockTimelineModel); const timelines = getTimelinesInStorageByIds(storage, []); expect(timelines).toEqual({}); }); it('returns empty timelime when a specific timeline does not exists', () => { const timelineStorage = useTimelinesStorage(); - timelineStorage.addTimeline('hosts-page-events', mockTimelineModel); - const timelines = getTimelinesInStorageByIds(storage, ['hosts-page-external-alerts']); + timelineStorage.addTimeline(TimelineId.hostsPageEvents, mockTimelineModel); + const timelines = getTimelinesInStorageByIds(storage, [TimelineId.hostsPageExternalAlerts]); expect(timelines).toEqual({}); }); it('returns timelines correctly when one exist and another not', () => { const timelineStorage = useTimelinesStorage(); - timelineStorage.addTimeline('hosts-page-events', mockTimelineModel); + timelineStorage.addTimeline(TimelineId.hostsPageEvents, mockTimelineModel); const timelines = getTimelinesInStorageByIds(storage, [ - 'hosts-page-events', - 'hosts-page-external-alerts', + TimelineId.hostsPageEvents, + TimelineId.hostsPageExternalAlerts, ]); expect(timelines).toEqual({ - 'hosts-page-events': mockTimelineModel, + [TimelineId.hostsPageEvents]: mockTimelineModel, }); }); }); @@ -131,12 +133,12 @@ describe('SiemLocalStorage', () => { describe('getAllTimelinesInStorage', () => { it('gets timelines correctly', () => { const timelineStorage = useTimelinesStorage(); - timelineStorage.addTimeline('hosts-page-events', mockTimelineModel); - timelineStorage.addTimeline('hosts-page-external-alerts', mockTimelineModel); + timelineStorage.addTimeline(TimelineId.hostsPageEvents, mockTimelineModel); + timelineStorage.addTimeline(TimelineId.hostsPageExternalAlerts, mockTimelineModel); const timelines = getAllTimelinesInStorage(storage); expect(timelines).toEqual({ - 'hosts-page-events': mockTimelineModel, - 'hosts-page-external-alerts': mockTimelineModel, + [TimelineId.hostsPageEvents]: mockTimelineModel, + [TimelineId.hostsPageExternalAlerts]: mockTimelineModel, }); }); @@ -148,18 +150,18 @@ describe('SiemLocalStorage', () => { describe('addTimelineInStorage', () => { it('adds a timeline when storage is empty', () => { - addTimelineInStorage(storage, 'hosts-page-events', mockTimelineModel); + addTimelineInStorage(storage, TimelineId.hostsPageEvents, mockTimelineModel); expect(JSON.parse(localStorage.getItem(LOCAL_STORAGE_TIMELINE_KEY))).toEqual({ - 'hosts-page-events': mockTimelineModel, + [TimelineId.hostsPageEvents]: mockTimelineModel, }); }); it('adds a timeline when storage contains another timelines', () => { - addTimelineInStorage(storage, 'hosts-page-events', mockTimelineModel); - addTimelineInStorage(storage, 'hosts-page-external-alerts', mockTimelineModel); + addTimelineInStorage(storage, TimelineId.hostsPageEvents, mockTimelineModel); + addTimelineInStorage(storage, TimelineId.hostsPageExternalAlerts, mockTimelineModel); expect(JSON.parse(localStorage.getItem(LOCAL_STORAGE_TIMELINE_KEY))).toEqual({ - 'hosts-page-events': mockTimelineModel, - 'hosts-page-external-alerts': mockTimelineModel, + [TimelineId.hostsPageEvents]: mockTimelineModel, + [TimelineId.hostsPageExternalAlerts]: mockTimelineModel, }); }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.tsx index 8c03ae7b1a58d7..1a09868da77716 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.tsx @@ -5,16 +5,17 @@ */ import { Storage } from '../../../../../../../src/plugins/kibana_utils/public'; -import { TimelinesStorage, TimelineId } from './types'; +import { TimelinesStorage } from './types'; import { useKibana } from '../../../common/lib/kibana'; import { TimelineModel } from '../../store/timeline/model'; +import { TimelineIdLiteral } from '../../../../common/types/timeline'; export const LOCAL_STORAGE_TIMELINE_KEY = 'timelines'; const EMPTY_TIMELINE = {} as { - [K in TimelineId]: TimelineModel; + [K in TimelineIdLiteral]: TimelineModel; }; -export const getTimelinesInStorageByIds = (storage: Storage, timelineIds: TimelineId[]) => { +export const getTimelinesInStorageByIds = (storage: Storage, timelineIds: TimelineIdLiteral[]) => { const allTimelines = storage.get(LOCAL_STORAGE_TIMELINE_KEY); if (!allTimelines) { @@ -33,13 +34,17 @@ export const getTimelinesInStorageByIds = (storage: Storage, timelineIds: Timeli ...acc, [timelineId]: timelineModel, }; - }, {} as { [K in TimelineId]: TimelineModel }); + }, {} as { [K in TimelineIdLiteral]: TimelineModel }); }; export const getAllTimelinesInStorage = (storage: Storage) => storage.get(LOCAL_STORAGE_TIMELINE_KEY) ?? {}; -export const addTimelineInStorage = (storage: Storage, id: TimelineId, timeline: TimelineModel) => { +export const addTimelineInStorage = ( + storage: Storage, + id: TimelineIdLiteral, + timeline: TimelineModel +) => { const timelines = getAllTimelinesInStorage(storage); storage.set(LOCAL_STORAGE_TIMELINE_KEY, { ...timelines, @@ -53,7 +58,7 @@ export const useTimelinesStorage = (): TimelinesStorage => { const getAllTimelines: TimelinesStorage['getAllTimelines'] = () => getAllTimelinesInStorage(storage); - const getTimelineById: TimelinesStorage['getTimelineById'] = (id: TimelineId) => + const getTimelineById: TimelinesStorage['getTimelineById'] = (id: TimelineIdLiteral) => getTimelinesInStorageByIds(storage, [id])[id] ?? null; const addTimeline: TimelinesStorage['addTimeline'] = (id, timeline) => diff --git a/x-pack/plugins/security_solution/public/timelines/containers/local_storage/types.ts b/x-pack/plugins/security_solution/public/timelines/containers/local_storage/types.ts index d7d96c1e90abbc..d888f3bb8b332e 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/local_storage/types.ts +++ b/x-pack/plugins/security_solution/public/timelines/containers/local_storage/types.ts @@ -5,21 +5,10 @@ */ import { TimelineModel } from '../../../timelines/store/timeline/model'; +import { TimelineIdLiteral } from '../../../../common/types/timeline'; -export type HOST_PAGE_EVENTS_TIMELINE_ID = 'hosts-page-events'; -export type HOST_PAGE_EXTERNAL_EVENTS_TIMELINE_ID = 'hosts-page-external-alerts'; -export type ALERTS_PAGE_SINGLE_RULE_TIMELINE_ID = 'alerts-rules-details-page'; -export type ALERTS_PAGE_TIMELINE_ID = 'alerts-page'; -export type NETWORK_PAGE_EXTERNAL_EVENTS_TIMELINE_ID = 'network-page-external-alerts'; - -export type TimelineId = - | HOST_PAGE_EVENTS_TIMELINE_ID - | HOST_PAGE_EXTERNAL_EVENTS_TIMELINE_ID - | ALERTS_PAGE_SINGLE_RULE_TIMELINE_ID - | ALERTS_PAGE_TIMELINE_ID - | NETWORK_PAGE_EXTERNAL_EVENTS_TIMELINE_ID; export interface TimelinesStorage { - getAllTimelines: () => Record; - getTimelineById: (id: TimelineId) => TimelineModel | null; - addTimeline: (id: TimelineId, timeline: TimelineModel) => void; + getAllTimelines: () => Record; + getTimelineById: (id: TimelineIdLiteral) => TimelineModel | null; + addTimeline: (id: TimelineIdLiteral, timeline: TimelineModel) => void; } diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.ts index 511dc8311430ae..b3d1db23ffae8a 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.ts @@ -9,8 +9,8 @@ import { map, filter, ignoreElements, tap, withLatestFrom, delay } from 'rxjs/op import { Epic } from 'redux-observable'; import { get } from 'lodash/fp'; +import { TimelineIdLiteral } from '../../../../common/types/timeline'; import { addTimelineInStorage } from '../../containers/local_storage'; -import { TimelineId } from '../../containers/local_storage/types'; import { removeColumn, @@ -50,7 +50,7 @@ export const createTimelineLocalStorageEpic = (): Epic< tap(([action, timelineById]) => { if (timelineActionTypes.includes(action.type)) { if (storage) { - const timelineId: TimelineId = get('payload.id', action); + const timelineId: TimelineIdLiteral = get('payload.id', action); addTimelineInStorage(storage, timelineId, timelineById[timelineId]); } }