From d1d567f33654fce90a6714900ddc5d61dffe9b08 Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Tue, 23 Mar 2021 23:37:05 +0100 Subject: [PATCH 01/41] [Cases] Add Lens markdown plugin --- x-pack/plugins/security_solution/kibana.json | 1 + .../markdown_editor/plugins/index.ts | 4 + .../markdown_editor/plugins/lens/constants.ts | 9 ++ .../markdown_editor/plugins/lens/index.ts | 12 +++ .../markdown_editor/plugins/lens/parser.ts | 80 +++++++++++++++ .../markdown_editor/plugins/lens/plugin.tsx | 97 +++++++++++++++++++ .../plugins/lens/processor.tsx | 37 +++++++ .../plugins/lens/translations.ts | 55 +++++++++++ .../markdown_editor/plugins/lens/types.ts | 19 ++++ .../plugins/security_solution/public/types.ts | 2 + 10 files changed, 316 insertions(+) create mode 100644 x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/constants.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/index.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/parser.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/processor.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/translations.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/types.ts diff --git a/x-pack/plugins/security_solution/kibana.json b/x-pack/plugins/security_solution/kibana.json index d4551f76ae390..cfad3b913f15f 100644 --- a/x-pack/plugins/security_solution/kibana.json +++ b/x-pack/plugins/security_solution/kibana.json @@ -20,6 +20,7 @@ ], "optionalPlugins": [ "encryptedSavedObjects", + "lens", "fleet", "ml", "newsfeed", diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/index.ts b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/index.ts index bc0da84133e68..a3096a0790216 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/index.ts +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/index.ts @@ -13,13 +13,17 @@ import { } from '@elastic/eui'; import * as timelineMarkdownPlugin from './timeline'; +import * as lensMarkdownPlugin from './lens'; const uiPlugins: EuiMarkdownEditorUiPlugin[] = getDefaultEuiMarkdownUiPlugins(); uiPlugins.push(timelineMarkdownPlugin.plugin); +uiPlugins.push(lensMarkdownPlugin.plugin); export { uiPlugins }; export const parsingPlugins = getDefaultEuiMarkdownParsingPlugins(); export const processingPlugins = getDefaultEuiMarkdownProcessingPlugins(); parsingPlugins.push(timelineMarkdownPlugin.parser); +parsingPlugins.push(lensMarkdownPlugin.parser); // This line of code is TS-compatible and it will break if [1][1] change in the future. processingPlugins[1][1].components.timeline = timelineMarkdownPlugin.renderer; +processingPlugins[1][1].components.lens = lensMarkdownPlugin.renderer; diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/constants.ts b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/constants.ts new file mode 100644 index 0000000000000..3b3d479fad301 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/constants.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +export const ID = 'lens'; +export const PREFIX = `[`; diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/index.ts b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/index.ts new file mode 100644 index 0000000000000..aa01ac0555c1e --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/index.ts @@ -0,0 +1,12 @@ +/* + * 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. + */ + +import { plugin } from './plugin'; +import { LensParser } from './parser'; +import { LensMarkDownRenderer } from './processor'; + +export { plugin, LensParser as parser, LensMarkDownRenderer as renderer }; diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/parser.ts b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/parser.ts new file mode 100644 index 0000000000000..fa930d05ebd86 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/parser.ts @@ -0,0 +1,80 @@ +/* + * 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. + */ + +import { Plugin } from 'unified'; +import { RemarkTokenizer } from '@elastic/eui'; +import { parse } from 'query-string'; +import { decodeRisonUrlState } from '../../../url_state/helpers'; +import { ID, PREFIX } from './constants'; +import * as i18n from './translations'; + +export const LensParser: Plugin = function () { + const Parser = this.Parser; + const tokenizers = Parser.prototype.blockTokenizers; + const methods = Parser.prototype.blockMethods; + + const tokenizeLens: RemarkTokenizer = function (eat, value, silent) { + if (value.startsWith('!{lens') === false) return false; + + const nextChar = value[6]; + + if (nextChar !== '{' && nextChar !== '}') return false; // this isn't actually a lens + + if (silent) { + return true; + } + + // is there a configuration? + const hasConfiguration = nextChar === '{'; + + let match = '!{lens'; + let configuration = {}; + + if (hasConfiguration) { + let configurationString = ''; + + let openObjects = 0; + + for (let i = 6; i < value.length; i++) { + const char = value[i]; + if (char === '{') { + openObjects++; + configurationString += char; + } else if (char === '}') { + openObjects--; + if (openObjects === -1) { + break; + } + configurationString += char; + } else { + configurationString += char; + } + } + + match += configurationString; + try { + configuration = JSON.parse(configurationString); + } catch (e) { + const now = eat.now(); + this.file.fail(`Unable to parse lens JSON configuration: ${e}`, { + line: now.line, + column: now.column + 6, + }); + } + } + + match += '}'; + + return eat(match)({ + type: 'lens', + ...configuration, + }); + }; + + tokenizers.lens = tokenizeLens; + methods.splice(methods.indexOf('text'), 0, 'lens'); +}; diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx new file mode 100644 index 0000000000000..133a0f22804a7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx @@ -0,0 +1,97 @@ +/* + * 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. + */ + +import React, { useCallback, memo } from 'react'; +import { + EuiSelectableOption, + EuiModalBody, + EuiModalHeader, + EuiMarkdownEditorUiPlugin, + EuiCodeBlock, +} from '@elastic/eui'; + +// import { LensType } from '../../../../../../common/types/lens'; +// import { SelectableLens } from '../../../../../lenss/components/lens/selectable_lens'; +// import { OpenLensResult } from '../../../../../lenss/components/open_lens/types'; +// import { getLensUrl, useFormatUrl } from '../../../link_to'; + +import { ID } from './constants'; +import * as i18n from './translations'; +import { SecurityPageName } from '../../../../../app/types'; + +interface LensEditorProps { + onClosePopover: () => void; + onInsert: (markdown: string, config: { block: boolean }) => void; +} + +const LensEditorComponent: React.FC = ({ onClosePopover, onInsert }) => { + // const { formatUrl } = useFormatUrl(SecurityPageName.lenss); + + // const handleGetSelectableOptions = useCallback( + // ({ lenss }: { lenss: OpenLensResult[] }) => [ + // ...lenss.map( + // (t: OpenLensResult, index: number) => + // ({ + // description: t.description, + // favorite: t.favorite, + // label: t.title, + // id: t.savedObjectId, + // key: `${t.title}-${index}`, + // title: t.title, + // checked: undefined, + // } as EuiSelectableOption) + // ), + // ], + // [] + // ); + + // const handleLensChange = useCallback( + // (lensTitle, lensId, graphEventId) => { + // const url = formatUrl(getLensUrl(lensId ?? '', graphEventId), { + // absolute: true, + // skipSearch: true, + // }); + // onInsert(`[${lensTitle}](${url})`, { + // block: false, + // }); + // }, + // [formatUrl, onInsert] + // ); + + return ( + <> + + + {/* */} + + + ); +}; + +const LensEditor = memo(LensEditorComponent); + +export const plugin: EuiMarkdownEditorUiPlugin = { + name: ID, + button: { + label: i18n.INSERT_LENS, + iconType: 'lensApp', + }, + helpText: ( + + {'[title](url)'} + + ), + editor: function editor({ node, onSave, onCancel }) { + return ; + }, +}; diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/processor.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/processor.tsx new file mode 100644 index 0000000000000..308172572953e --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/processor.tsx @@ -0,0 +1,37 @@ +/* + * 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. + */ + +import React, { useCallback, memo, useEffect, useState } from 'react'; +import { EuiToolTip, EuiLink, EuiMarkdownAstNodePosition } from '@elastic/eui'; + +// import { useLensClick } from '../../../../utils/lens/use_lens_click'; +import { LensProps } from './types'; +import * as i18n from './translations'; +import { useKibana } from '../../../../lib/kibana'; + +export const LensMarkDownRendererComponent: React.FC< + LensProps & { + position: EuiMarkdownAstNodePosition; + } +> = ({ id, title, graphEventId }) => { + const kibana = useKibana(); + const LensComponent = kibana?.services?.lens?.EmbeddableComponent!; + + return ( + + ); +}; + +export const LensMarkDownRenderer = LensMarkDownRendererComponent; diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/translations.ts b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/translations.ts new file mode 100644 index 0000000000000..476a4bfdfe0bc --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/translations.ts @@ -0,0 +1,55 @@ +/* + * 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. + */ + +import { i18n } from '@kbn/i18n'; + +export const INSERT_LENS = i18n.translate( + 'xpack.securitySolution.markdownEditor.plugins.lens.insertLensButtonLabel', + { + defaultMessage: 'Insert lens link', + } +); + +export const LENS_ID = (lensId: string) => + i18n.translate('xpack.securitySolution.markdownEditor.plugins.lens.toolTip.lensId', { + defaultMessage: 'Lens id: { lensId }', + values: { + lensId, + }, + }); + +export const NO_LENS_NAME_FOUND = i18n.translate( + 'xpack.securitySolution.markdownEditor.plugins.lens.noLensNameFoundErrorMsg', + { + defaultMessage: 'No lens name found', + } +); + +export const NO_LENS_ID_FOUND = i18n.translate( + 'xpack.securitySolution.markdownEditor.plugins.lens.noLensIdFoundErrorMsg', + { + defaultMessage: 'No lens id found', + } +); + +export const LENS_URL_IS_NOT_VALID = (lensUrl: string) => + i18n.translate( + 'xpack.securitySolution.markdownEditor.plugins.lens.toolTip.lensUrlIsNotValidErrorMsg', + { + defaultMessage: 'Lens URL is not valid => {lensUrl}', + values: { + lensUrl, + }, + } + ); + +export const NO_PARENTHESES = i18n.translate( + 'xpack.securitySolution.markdownEditor.plugins.lens.noParenthesesErrorMsg', + { + defaultMessage: 'Expected left parentheses', + } +); diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/types.ts b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/types.ts new file mode 100644 index 0000000000000..58ea34ecd9cba --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/types.ts @@ -0,0 +1,19 @@ +/* + * 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. + */ + +import { ID } from './constants'; + +export interface LensConfiguration { + id: string | null; + title: string; + graphEventId?: string; + [key: string]: string | null | undefined; +} + +export interface LensProps extends LensConfiguration { + type: typeof ID; +} diff --git a/x-pack/plugins/security_solution/public/types.ts b/x-pack/plugins/security_solution/public/types.ts index e88077679e1b6..05e75334f6fe5 100644 --- a/x-pack/plugins/security_solution/public/types.ts +++ b/x-pack/plugins/security_solution/public/types.ts @@ -10,6 +10,7 @@ import { CoreStart } from '../../../../src/core/public'; import { HomePublicPluginSetup } from '../../../../src/plugins/home/public'; import { DataPublicPluginStart } from '../../../../src/plugins/data/public'; import { EmbeddableStart } from '../../../../src/plugins/embeddable/public'; +import { LensPublicStart } from '../../../plugins/lens/public'; import { NewsfeedPublicPluginStart } from '../../../../src/plugins/newsfeed/public'; import { Start as InspectorStart } from '../../../../src/plugins/inspector/public'; import { UiActionsStart } from '../../../../src/plugins/ui_actions/public'; @@ -51,6 +52,7 @@ export interface StartPlugins { embeddable: EmbeddableStart; inspector: InspectorStart; fleet?: FleetStart; + lens?: LensPublicStart; lists?: ListsPluginStart; licensing: LicensingPluginStart; newsfeed?: NewsfeedPublicPluginStart; From 4b465901ce128fa2f255ed2396c4afa0632ad37f Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Fri, 9 Apr 2021 18:03:53 +0200 Subject: [PATCH 02/41] cleanup --- .../markdown_editor/plugins/lens/constants.ts | 1 + .../markdown_editor/plugins/lens/parser.ts | 4 - .../markdown_editor/plugins/lens/plugin.tsx | 218 ++++++++++++++---- .../plugins/lens/processor.tsx | 68 ++++-- .../markdown_editor/plugins/lens/types.ts | 19 -- 5 files changed, 216 insertions(+), 94 deletions(-) delete mode 100644 x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/types.ts diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/constants.ts b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/constants.ts index 3b3d479fad301..a88051d8e4542 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/constants.ts +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/constants.ts @@ -7,3 +7,4 @@ export const ID = 'lens'; export const PREFIX = `[`; +export const LENS_VISUALIZATION_HEIGHT = 200; diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/parser.ts b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/parser.ts index fa930d05ebd86..5d4ec22b93e2b 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/parser.ts +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/parser.ts @@ -7,10 +7,6 @@ import { Plugin } from 'unified'; import { RemarkTokenizer } from '@elastic/eui'; -import { parse } from 'query-string'; -import { decodeRisonUrlState } from '../../../url_state/helpers'; -import { ID, PREFIX } from './constants'; -import * as i18n from './translations'; export const LensParser: Plugin = function () { const Parser = this.Parser; diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx index 133a0f22804a7..9dba1bb446294 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx @@ -5,80 +5,187 @@ * 2.0. */ -import React, { useCallback, memo } from 'react'; import { - EuiSelectableOption, + EuiComboBox, EuiModalBody, EuiModalHeader, + EuiModalHeaderTitle, EuiMarkdownEditorUiPlugin, EuiCodeBlock, + EuiSpacer, + EuiModalFooter, + EuiButtonEmpty, + EuiButton, + EuiFlexItem, + EuiFlexGroup, + EuiFormRow, + EuiDatePicker, + EuiDatePickerRange, } from '@elastic/eui'; +import React, { useCallback, useEffect, useState } from 'react'; +import styled from 'styled-components'; +import moment, { Moment } from 'moment'; -// import { LensType } from '../../../../../../common/types/lens'; -// import { SelectableLens } from '../../../../../lenss/components/lens/selectable_lens'; -// import { OpenLensResult } from '../../../../../lenss/components/open_lens/types'; -// import { getLensUrl, useFormatUrl } from '../../../link_to'; - +import { useKibana } from '../../../../lib/kibana'; +import { LensMarkDownRenderer } from './processor'; import { ID } from './constants'; import * as i18n from './translations'; -import { SecurityPageName } from '../../../../../app/types'; + +const ModalContainer = styled.div` + width: ${({ theme }) => `${theme.eui.euiBreakpoints.m}px`}; +`; interface LensEditorProps { + id?: string | null; + title?: string | null; + startDate?: Moment | null; + endDate?: Moment | null; onClosePopover: () => void; onInsert: (markdown: string, config: { block: boolean }) => void; } -const LensEditorComponent: React.FC = ({ onClosePopover, onInsert }) => { - // const { formatUrl } = useFormatUrl(SecurityPageName.lenss); +const LensEditorComponent: React.FC = ({ + id, + title, + startDate: defaultStartDate, + endDate: defaultEndDate, + onClosePopover, + onInsert, +}) => { + const services = useKibana().services; + const [lensOptions, setLensOptions] = useState>([]); + const [selectedOptions, setSelectedOptions] = useState>( + id && title ? [{ value: id, label: title }] : [] + ); + const [lensSavedObjectId, setLensSavedObjectId] = useState(id ?? null); + const [startDate, setStartDate] = useState( + defaultStartDate ? moment(defaultStartDate) : moment().subtract(7, 'd') + ); + const [endDate, setEndDate] = useState( + defaultEndDate ? moment(defaultEndDate) : moment() + ); + + useEffect(() => { + const fetchLensSavedObjects = async () => { + const { savedObjects } = await services.savedObjects.client.find({ + type: 'lens', + perPage: 1000, + }); + const options = savedObjects.map((lensSO) => ({ + label: lensSO.attributes.title, + value: lensSO.id, + })); + + setLensOptions(options); + }; + const fetchTagsSavedObjects = async () => { + const tags = await services.savedObjects.client.find({ + type: 'tag', + perPage: 1000, + }); + }; + fetchLensSavedObjects(); + fetchTagsSavedObjects(); + }, []); + + const handleChange = useCallback((options) => { + setSelectedOptions(options); + setLensSavedObjectId(options[0] ? options[0].value : null); + }, []); + + console.error(';ss', lensOptions); - // const handleGetSelectableOptions = useCallback( - // ({ lenss }: { lenss: OpenLensResult[] }) => [ - // ...lenss.map( - // (t: OpenLensResult, index: number) => - // ({ - // description: t.description, - // favorite: t.favorite, - // label: t.title, - // id: t.savedObjectId, - // key: `${t.title}-${index}`, - // title: t.title, - // checked: undefined, - // } as EuiSelectableOption) - // ), - // ], - // [] - // ); + const handleLensDateChange = useCallback((data) => { + if (data.range?.length === 2) { + setStartDate(moment(data.range[0])); + setEndDate(moment(data.range[1])); + } + }, []); - // const handleLensChange = useCallback( - // (lensTitle, lensId, graphEventId) => { - // const url = formatUrl(getLensUrl(lensId ?? '', graphEventId), { - // absolute: true, - // skipSearch: true, - // }); - // onInsert(`[${lensTitle}](${url})`, { - // block: false, - // }); - // }, - // [formatUrl, onInsert] - // ); + const handleAdd = useCallback(() => { + if (lensSavedObjectId && selectedOptions[0]) { + onInsert( + `!{lens${JSON.stringify({ + id: lensSavedObjectId, + startDate, + endDate, + title: selectedOptions[0].label, + })}}`, + { + block: true, + } + ); + } + }, [lensSavedObjectId, selectedOptions, onInsert, startDate, endDate]); return ( - <> - + + + + {id && title ? 'Edit Lens visualization' : 'Add Lens visualization'} + + - {/* */} + + + + + + + + + endDate : false} + aria-label="Start date" + showTimeSelect + /> + } + endDateControl={ + endDate : false} + aria-label="End date" + showTimeSelect + /> + } + /> + + + + + - + + {'Cancel'} + + {'Add to a Case'} + + + ); }; -const LensEditor = memo(LensEditorComponent); +const LensEditor = React.memo(LensEditorComponent); export const plugin: EuiMarkdownEditorUiPlugin = { name: ID, @@ -92,6 +199,15 @@ export const plugin: EuiMarkdownEditorUiPlugin = { ), editor: function editor({ node, onSave, onCancel }) { - return ; + return ( + + ); }, }; diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/processor.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/processor.tsx index 308172572953e..d3063bab1ac32 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/processor.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/processor.tsx @@ -5,33 +5,61 @@ * 2.0. */ -import React, { useCallback, memo, useEffect, useState } from 'react'; -import { EuiToolTip, EuiLink, EuiMarkdownAstNodePosition } from '@elastic/eui'; +import uuid from 'uuid'; +import React from 'react'; +import { EuiText, EuiSpacer } from '@elastic/eui'; +import styled from 'styled-components'; +import { Moment } from 'moment'; -// import { useLensClick } from '../../../../utils/lens/use_lens_click'; -import { LensProps } from './types'; -import * as i18n from './translations'; +import { EmbeddableComponentProps } from '../../../../../../../lens/public'; import { useKibana } from '../../../../lib/kibana'; +import { LENS_VISUALIZATION_HEIGHT } from './constants'; -export const LensMarkDownRendererComponent: React.FC< - LensProps & { - position: EuiMarkdownAstNodePosition; - } -> = ({ id, title, graphEventId }) => { +const Container = styled.div` + min-height: ${LENS_VISUALIZATION_HEIGHT}px; +`; + +interface LensMarkDownRendererProps { + id?: string | null; + title?: string | null; + startDate?: Moment | null; + endDate?: Moment | null; + onBrushEnd?: EmbeddableComponentProps['onBrushEnd']; +} + +const LensMarkDownRendererComponent: React.FC = ({ + id, + title, + startDate, + endDate, + onBrushEnd, +}) => { const kibana = useKibana(); const LensComponent = kibana?.services?.lens?.EmbeddableComponent!; return ( - + + {id ? ( + <> + +
{title}
+
+ + + + ) : null} +
); }; -export const LensMarkDownRenderer = LensMarkDownRendererComponent; +export const LensMarkDownRenderer = React.memo(LensMarkDownRendererComponent); diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/types.ts b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/types.ts deleted file mode 100644 index 58ea34ecd9cba..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/types.ts +++ /dev/null @@ -1,19 +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. - */ - -import { ID } from './constants'; - -export interface LensConfiguration { - id: string | null; - title: string; - graphEventId?: string; - [key: string]: string | null | undefined; -} - -export interface LensProps extends LensConfiguration { - type: typeof ID; -} From 7d49b2db99432cab7947813abe416ded6c0e31b5 Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Fri, 9 Apr 2021 19:14:24 +0200 Subject: [PATCH 03/41] cleanup --- .../markdown_editor/plugins/lens/plugin.tsx | 22 ++++++------------- .../plugins/lens/processor.tsx | 14 +++++++++--- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx index 9dba1bb446294..1ba7a5542dbeb 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx @@ -26,13 +26,14 @@ import React, { useCallback, useEffect, useState } from 'react'; import styled from 'styled-components'; import moment, { Moment } from 'moment'; +import { TypedLensByValueInput } from '../../../../../../../lens/public'; import { useKibana } from '../../../../lib/kibana'; import { LensMarkDownRenderer } from './processor'; import { ID } from './constants'; import * as i18n from './translations'; const ModalContainer = styled.div` - width: ${({ theme }) => `${theme.eui.euiBreakpoints.m}px`}; + width: ${({ theme }) => theme.eui.euiBreakpoints.m}; `; interface LensEditorProps { @@ -52,7 +53,7 @@ const LensEditorComponent: React.FC = ({ onClosePopover, onInsert, }) => { - const services = useKibana().services; + const soClient = useKibana().services.savedObjects.client; const [lensOptions, setLensOptions] = useState>([]); const [selectedOptions, setSelectedOptions] = useState>( id && title ? [{ value: id, label: title }] : [] @@ -67,7 +68,7 @@ const LensEditorComponent: React.FC = ({ useEffect(() => { const fetchLensSavedObjects = async () => { - const { savedObjects } = await services.savedObjects.client.find({ + const { savedObjects } = await soClient.find({ type: 'lens', perPage: 1000, }); @@ -78,23 +79,14 @@ const LensEditorComponent: React.FC = ({ setLensOptions(options); }; - const fetchTagsSavedObjects = async () => { - const tags = await services.savedObjects.client.find({ - type: 'tag', - perPage: 1000, - }); - }; fetchLensSavedObjects(); - fetchTagsSavedObjects(); - }, []); + }, [soClient]); const handleChange = useCallback((options) => { setSelectedOptions(options); setLensSavedObjectId(options[0] ? options[0].value : null); }, []); - console.error(';ss', lensOptions); - const handleLensDateChange = useCallback((data) => { if (data.range?.length === 2) { setStartDate(moment(data.range[0])); @@ -170,8 +162,8 @@ const LensEditorComponent: React.FC = ({ diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/processor.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/processor.tsx index d3063bab1ac32..e7754a338e89c 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/processor.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/processor.tsx @@ -9,8 +9,8 @@ import uuid from 'uuid'; import React from 'react'; import { EuiText, EuiSpacer } from '@elastic/eui'; import styled from 'styled-components'; -import { Moment } from 'moment'; +import { createGlobalStyle } from '../../../../../../../../../src/plugins/kibana_react/common'; import { EmbeddableComponentProps } from '../../../../../../../lens/public'; import { useKibana } from '../../../../lib/kibana'; import { LENS_VISUALIZATION_HEIGHT } from './constants'; @@ -19,11 +19,18 @@ const Container = styled.div` min-height: ${LENS_VISUALIZATION_HEIGHT}px; `; +// when displaying chart in modal the tooltip is render under the modal +const LensChartTooltipFix = createGlobalStyle` + div.euiOverlayMask.euiOverlayMask--aboveHeader ~ [id^='echTooltipPortal'] { + z-index: ${({ theme }) => theme.eui.euiZLevel7} !important; + } +`; + interface LensMarkDownRendererProps { id?: string | null; title?: string | null; - startDate?: Moment | null; - endDate?: Moment | null; + startDate?: string | null; + endDate?: string | null; onBrushEnd?: EmbeddableComponentProps['onBrushEnd']; } @@ -56,6 +63,7 @@ const LensMarkDownRendererComponent: React.FC = ({ savedObjectId={id} onBrushEnd={onBrushEnd} /> + ) : null} From 60146c5d83e3b026c9a7684a5b526e4c271dd8a9 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Mon, 14 Jun 2021 08:36:55 -0600 Subject: [PATCH 04/41] add lens to cases --- .../public/components/case_view/index.tsx | 41 +++++++------- .../public/components/lens_context/index.tsx | 54 +++++++++++++++++++ .../lens_context/use_lens_context.ts | 13 +++++ .../components/markdown_editor/types.ts | 2 +- .../components/markdown_editor/use_plugins.ts | 13 ++++- .../cases/components/case_view/index.tsx | 11 ++++ 6 files changed, 114 insertions(+), 20 deletions(-) create mode 100644 x-pack/plugins/cases/public/components/lens_context/index.tsx create mode 100644 x-pack/plugins/cases/public/components/lens_context/use_lens_context.ts diff --git a/x-pack/plugins/cases/public/components/case_view/index.tsx b/x-pack/plugins/cases/public/components/case_view/index.tsx index df57e49073a60..38fadf6f04c5f 100644 --- a/x-pack/plugins/cases/public/components/case_view/index.tsx +++ b/x-pack/plugins/cases/public/components/case_view/index.tsx @@ -43,6 +43,7 @@ import { useTimelineContext } from '../timeline_context/use_timeline_context'; import { CasesNavigation } from '../links'; import { OwnerProvider } from '../owner_context'; import { DoesNotExist } from './does_not_exist'; +import { CasesLensIntegration, CasesLensIntegrationProvider } from '../lens_context'; const gutterTimeline = '70px'; // seems to be a timeline reference from the original file export interface CaseViewComponentProps { @@ -60,6 +61,7 @@ export interface CaseViewComponentProps { } export interface CaseViewProps extends CaseViewComponentProps { + lensIntegration?: CasesLensIntegration; onCaseDataSuccess?: (data: Case) => void; timelineIntegration?: CasesTimelineIntegration; } @@ -485,6 +487,7 @@ export const CaseView = React.memo( caseId, configureCasesNavigation, getCaseDetailHrefWithCommentId, + lensIntegration, onCaseDataSuccess, onComponentInitialized, ruleDetailsNavigation, @@ -514,24 +517,26 @@ export const CaseView = React.memo( return ( data && ( - - - + + + + + ) ); diff --git a/x-pack/plugins/cases/public/components/lens_context/index.tsx b/x-pack/plugins/cases/public/components/lens_context/index.tsx new file mode 100644 index 0000000000000..d378a820c6328 --- /dev/null +++ b/x-pack/plugins/cases/public/components/lens_context/index.tsx @@ -0,0 +1,54 @@ +/* + * 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. + */ + +import React, { useState } from 'react'; +import { EuiMarkdownEditorUiPlugin, EuiMarkdownAstNodePosition } from '@elastic/eui'; +import { Plugin } from 'unified'; + +interface UseInsertLensReturn { + handleOnLensChange: (title: string, id: string | null, graphEventId?: string) => void; +} + +interface LensProcessingPluginRendererProps { + id: string | null; + title: string; + graphEventId?: string; + type: 'lens'; + [key: string]: string | null | undefined; +} + +export interface CasesLensIntegration { + editor_plugins: { + parsingPlugin: Plugin; + processingPluginRenderer: React.FC< + LensProcessingPluginRendererProps & { position: EuiMarkdownAstNodePosition } + >; + uiPlugin: EuiMarkdownEditorUiPlugin; + }; + // hooks: { + // useInsertLens: (value: string, onChange: (newValue: string) => void) => UseInsertLensReturn; + // }; + // ui?: { + // renderInvestigateInLensActionComponent?: (alertIds: string[]) => JSX.Element; + // renderLensDetailsPanel?: () => JSX.Element; + // }; +} + +// This context is available to all children of the stateful_event component where the provider is currently set +export const CasesLensIntegrationContext = React.createContext(null); + +export const CasesLensIntegrationProvider: React.FC<{ + lensIntegration?: CasesLensIntegration; +}> = ({ children, lensIntegration }) => { + const [activeLensIntegration] = useState(lensIntegration ?? null); + + return ( + + {children} + + ); +}; diff --git a/x-pack/plugins/cases/public/components/lens_context/use_lens_context.ts b/x-pack/plugins/cases/public/components/lens_context/use_lens_context.ts new file mode 100644 index 0000000000000..8f3c3d4b67d60 --- /dev/null +++ b/x-pack/plugins/cases/public/components/lens_context/use_lens_context.ts @@ -0,0 +1,13 @@ +/* + * 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. + */ + +import { useContext } from 'react'; +import { CasesLensIntegrationContext } from '.'; + +export const useLensContext = () => { + return useContext(CasesLensIntegrationContext); +}; diff --git a/x-pack/plugins/cases/public/components/markdown_editor/types.ts b/x-pack/plugins/cases/public/components/markdown_editor/types.ts index bb932f2fcfe22..b38d6a820b502 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/types.ts +++ b/x-pack/plugins/cases/public/components/markdown_editor/types.ts @@ -23,7 +23,7 @@ export type TemporaryProcessingPluginsType = [ [ typeof rehype2react, Parameters[0] & { - components: { a: FunctionComponent; timeline: unknown }; + components: { a: FunctionComponent; lens: unknown; timeline: unknown }; } ], ...PluggableList diff --git a/x-pack/plugins/cases/public/components/markdown_editor/use_plugins.ts b/x-pack/plugins/cases/public/components/markdown_editor/use_plugins.ts index e98af8bca8bce..02c55b5111ab7 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/use_plugins.ts +++ b/x-pack/plugins/cases/public/components/markdown_editor/use_plugins.ts @@ -13,9 +13,11 @@ import { import { useMemo } from 'react'; import { useTimelineContext } from '../timeline_context/use_timeline_context'; import { TemporaryProcessingPluginsType } from './types'; +import { useLensContext } from '../lens_context/use_lens_context'; export const usePlugins = () => { const timelinePlugins = useTimelineContext()?.editor_plugins; + const lensPlugins = useLensContext()?.editor_plugins; return useMemo(() => { const uiPlugins = getDefaultEuiMarkdownUiPlugins(); @@ -31,10 +33,19 @@ export const usePlugins = () => { processingPlugins[1][1].components.timeline = timelinePlugins.processingPluginRenderer; } + if (lensPlugins) { + uiPlugins.push(lensPlugins.uiPlugin); + + parsingPlugins.push(lensPlugins.parsingPlugin); + + // This line of code is TS-compatible and it will break if [1][1] change in the future. + processingPlugins[1][1].components.lens = lensPlugins.processingPluginRenderer; + } + return { uiPlugins, parsingPlugins, processingPlugins, }; - }, [timelinePlugins]); + }, [lensPlugins, timelinePlugins]); }; diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx index b0f3ccb8c21ad..1509f101d39aa 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx @@ -36,6 +36,7 @@ import { SEND_ALERT_TO_TIMELINE } from './translations'; import { useInsertTimeline } from '../use_insert_timeline'; import { SpyRoute } from '../../../common/utils/route/spy_routes'; import * as timelineMarkdownPlugin from '../../../common/components/markdown_editor/plugins/timeline'; +import * as lensMarkdownPlugin from '../../../common/components/markdown_editor/plugins/lens'; interface Props { caseId: string; @@ -223,6 +224,16 @@ export const CaseView = React.memo(({ caseId, subCaseId, userCanCrud }: Props) = onClick: onConfigureCasesNavClick, }, getCaseDetailHrefWithCommentId, + lensIntegration: { + editor_plugins: { + parsingPlugin: lensMarkdownPlugin.parser, + processingPluginRenderer: lensMarkdownPlugin.renderer, + uiPlugin: lensMarkdownPlugin.plugin, + }, + // hooks: { + // useInsertLens, + // }, + }, onCaseDataSuccess, onComponentInitialized, ruleDetailsNavigation: { From ab7151920ac47eceea58dfea902abc0ba6dbabea Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Mon, 14 Jun 2021 09:47:44 -0600 Subject: [PATCH 05/41] remove comments --- .../cases/public/components/lens_context/index.tsx | 11 ----------- .../public/cases/components/case_view/index.tsx | 3 --- 2 files changed, 14 deletions(-) diff --git a/x-pack/plugins/cases/public/components/lens_context/index.tsx b/x-pack/plugins/cases/public/components/lens_context/index.tsx index d378a820c6328..311118d5f8724 100644 --- a/x-pack/plugins/cases/public/components/lens_context/index.tsx +++ b/x-pack/plugins/cases/public/components/lens_context/index.tsx @@ -9,10 +9,6 @@ import React, { useState } from 'react'; import { EuiMarkdownEditorUiPlugin, EuiMarkdownAstNodePosition } from '@elastic/eui'; import { Plugin } from 'unified'; -interface UseInsertLensReturn { - handleOnLensChange: (title: string, id: string | null, graphEventId?: string) => void; -} - interface LensProcessingPluginRendererProps { id: string | null; title: string; @@ -29,13 +25,6 @@ export interface CasesLensIntegration { >; uiPlugin: EuiMarkdownEditorUiPlugin; }; - // hooks: { - // useInsertLens: (value: string, onChange: (newValue: string) => void) => UseInsertLensReturn; - // }; - // ui?: { - // renderInvestigateInLensActionComponent?: (alertIds: string[]) => JSX.Element; - // renderLensDetailsPanel?: () => JSX.Element; - // }; } // This context is available to all children of the stateful_event component where the provider is currently set diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx index 1509f101d39aa..fa1414c089ff8 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx @@ -230,9 +230,6 @@ export const CaseView = React.memo(({ caseId, subCaseId, userCanCrud }: Props) = processingPluginRenderer: lensMarkdownPlugin.renderer, uiPlugin: lensMarkdownPlugin.plugin, }, - // hooks: { - // useInsertLens, - // }, }, onCaseDataSuccess, onComponentInitialized, From 48e2b82880b3125282c6886004d7dd63eedd73a0 Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Fri, 18 Jun 2021 10:25:18 +0200 Subject: [PATCH 06/41] [WIP] --- .../embeddable_state_transfer.ts | 1 + .../public/lib/state_transfer/types.ts | 1 + .../components/markdown_editor/editor.tsx | 93 ++++++++++++++----- .../components/user_action_tree/index.tsx | 6 +- .../lens/public/app_plugin/mounter.tsx | 1 + .../app_plugin/save_modal_container.tsx | 1 + x-pack/plugins/lens/public/plugin.ts | 24 +++-- .../scheduled_query_group_queries_table.tsx | 6 +- .../public/app/home/index.tsx | 10 +- .../cases/components/case_view/index.tsx | 1 + .../components/markdown_editor/editor.tsx | 2 + .../markdown_editor/plugins/lens/context.tsx | 10 ++ .../markdown_editor/plugins/lens/index.ts | 3 +- .../markdown_editor/plugins/lens/plugin.tsx | 29 +++++- .../plugins/lens/processor.tsx | 92 +++++++++++++----- 15 files changed, 217 insertions(+), 63 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/context.tsx diff --git a/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.ts b/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.ts index 26d366bb8dabe..a8f34d609e82a 100644 --- a/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.ts +++ b/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.ts @@ -174,6 +174,7 @@ export class EmbeddableStateTransfer { }, }; this.storage.set(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, stateObject); + console.error('navigateToWithState', appId, options, this.storage); await this.navigateToApp(appId, { path: options?.path, openInNewTab: options?.openInNewTab }); } } diff --git a/src/plugins/embeddable/public/lib/state_transfer/types.ts b/src/plugins/embeddable/public/lib/state_transfer/types.ts index 5e5ef9c360a64..0180aa8be2c92 100644 --- a/src/plugins/embeddable/public/lib/state_transfer/types.ts +++ b/src/plugins/embeddable/public/lib/state_transfer/types.ts @@ -17,6 +17,7 @@ export const EMBEDDABLE_EDITOR_STATE_KEY = 'embeddable_editor_state'; */ export interface EmbeddableEditorState { originatingApp: string; + originatingPath: string | undefined; embeddableId?: string; valueInput?: EmbeddableInput; } diff --git a/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx b/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx index 3d9e75c6450d0..60ea014bc5427 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx @@ -5,10 +5,11 @@ * 2.0. */ -import React, { memo, useEffect, useState, useCallback } from 'react'; +import React, { memo, useContext, useEffect, useRef, useState, useCallback } from 'react'; import { PluggableList } from 'unified'; -import { EuiMarkdownEditor, EuiMarkdownEditorUiPlugin } from '@elastic/eui'; +import { EuiMarkdownEditor, EuiMarkdownEditorUiPlugin, EuiMarkdownContext } from '@elastic/eui'; import { usePlugins } from './use_plugins'; +import { useLensContext } from '../lens_context/use_lens_context'; interface MarkdownEditorProps { ariaLabel: string; @@ -31,31 +32,79 @@ const MarkdownEditorComponent: React.FC = ({ value, }) => { const [markdownErrorMessages, setMarkdownErrorMessages] = useState([]); - const onParse = useCallback((err, { messages }) => { - setMarkdownErrorMessages(err ? [err] : messages); - }, []); + const { parsingPlugins, processingPlugins, uiPlugins } = usePlugins(); + const LensContextProvider = useLensContext()?.editor_context.Provider; + const editorRef = useRef(null); + const markdownContext = useContext(EuiMarkdownContext); + + console.error('markdownContext', markdownContext); + console.error('useRef', editorRef.current); - useEffect( - () => document.querySelector('textarea.euiMarkdownEditorTextArea')?.focus(), - [] + const onParse = useCallback( + (err, { messages }) => { + console.error('onParse', markdownContext); + markdownContext.openPluginEditor(uiPlugins[2]); + markdownContext.replaceNode( + { + start: { + offset: 0, + }, + end: { + offset: 0, + }, + }, + 'dupa' + ); + setMarkdownErrorMessages(err ? [err] : messages); + }, + [markdownContext, uiPlugins] ); - return ( - + const editor = ( + <> + + <>{`dupa`} + + ); + + // useEffect(() => { + // if (markdownContext) { + // console.error('uiPlugins', uiPlugins[2]); + // markdownContext.openPluginEditor(uiPlugins[2]); + // } + // document.querySelector('textarea.euiMarkdownEditorTextArea')?.focus(); + // }, []); + + // if (LensContextProvider) { + // return ( + // + // {editor} + // + // ); + // } + + return editor; }; export const MarkdownEditor = memo(MarkdownEditorComponent); diff --git a/x-pack/plugins/cases/public/components/user_action_tree/index.tsx b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx index f9bd941547078..8c4e69ae0ddd3 100644 --- a/x-pack/plugins/cases/public/components/user_action_tree/index.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx @@ -145,7 +145,7 @@ export const UserActionTree = React.memo( const [selectedOutlineCommentId, setSelectedOutlineCommentId] = useState(''); const { isLoadingIds, patchComment } = useUpdateComment(); const currentUser = useCurrentUser(); - const [manageMarkdownEditIds, setManangeMardownEditIds] = useState([]); + const [manageMarkdownEditIds, setManangeMarkdownEditIds] = useState([]); const [loadingAlertData, manualAlertsData] = useFetchAlertData( getManualAlertIdsWithNoRuleId(caseData.comments) @@ -154,9 +154,9 @@ export const UserActionTree = React.memo( const handleManageMarkdownEditId = useCallback( (id: string) => { if (!manageMarkdownEditIds.includes(id)) { - setManangeMardownEditIds([...manageMarkdownEditIds, id]); + setManangeMarkdownEditIds([...manageMarkdownEditIds, id]); } else { - setManangeMardownEditIds(manageMarkdownEditIds.filter((myId) => id !== myId)); + setManangeMarkdownEditIds(manageMarkdownEditIds.filter((myId) => id !== myId)); } }, [manageMarkdownEditIds] diff --git a/x-pack/plugins/lens/public/app_plugin/mounter.tsx b/x-pack/plugins/lens/public/app_plugin/mounter.tsx index 8e59f90c958f9..039fbf53e07b4 100644 --- a/x-pack/plugins/lens/public/app_plugin/mounter.tsx +++ b/x-pack/plugins/lens/public/app_plugin/mounter.tsx @@ -145,6 +145,7 @@ export async function mountApp( if (stateTransfer && props?.input) { const { input, isCopied } = props; stateTransfer.navigateToWithEmbeddablePackage(embeddableEditorIncomingState?.originatingApp, { + path: embeddableEditorIncomingState?.originatingPath, state: { embeddableId: isCopied ? undefined : embeddableEditorIncomingState.embeddableId, type: LENS_EMBEDDABLE_TYPE, diff --git a/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx b/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx index a65c8e6732e44..cd9c7e269e973 100644 --- a/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx +++ b/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx @@ -283,6 +283,7 @@ export const runSaveLensVisualization = async ( onAppLeave?.((actions) => { return actions.default(); }); + console.error('redirectToOrigin', newInput, saveProps); redirectToOrigin({ input: newInput, isCopied: saveProps.newCopyOnSave }); return; } else if (saveProps.dashboardId) { diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index 328bea5def557..3ff0318993a9c 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { AppMountParameters, CoreSetup, CoreStart } from 'kibana/public'; +import { AppMountParameters, CoreSetup, CoreStart, NavigateToAppOptions } from 'kibana/public'; import { UsageCollectionSetup, UsageCollectionStart } from 'src/plugins/usage_collection/public'; import { DataPublicPluginSetup, DataPublicPluginStart } from '../../../../src/plugins/data/public'; import { EmbeddableSetup, EmbeddableStart } from '../../../../src/plugins/embeddable/public'; @@ -111,7 +111,7 @@ export interface LensPublicStart { * * @experimental */ - navigateToPrefilledEditor: (input: LensEmbeddableInput, openInNewTab?: boolean) => void; + navigateToPrefilledEditor: (input: LensEmbeddableInput, options?: NavigateToAppOptions) => void; /** * Method which returns true if the user has permission to use Lens as defined by application capabilities. */ @@ -258,20 +258,28 @@ export class LensPlugin { return { EmbeddableComponent: getEmbeddableComponent(startDependencies.embeddable), SaveModalComponent: getSaveModalComponent(core, startDependencies, this.attributeService!), - navigateToPrefilledEditor: (input: LensEmbeddableInput, openInNewTab?: boolean) => { + navigateToPrefilledEditor: ( + input: LensEmbeddableInput, + options?: { + openInNewTab?: boolean; + originatingApp?: string; + originatingPath?: string; + } + ) => { // for openInNewTab, we set the time range in url via getEditPath below - if (input.timeRange && !openInNewTab) { + if (input.timeRange && !options?.openInNewTab) { startDependencies.data.query.timefilter.timefilter.setTime(input.timeRange); } const transfer = new EmbeddableStateTransfer( core.application.navigateToApp, core.application.currentAppId$ ); - transfer.navigateToEditor('lens', { - openInNewTab, - path: getEditPath(undefined, openInNewTab ? input.timeRange : undefined), + transfer.navigateToEditor(APP_ID, { + openInNewTab: options?.openInNewTab ?? false, + path: getEditPath(undefined, options?.openInNewTab ? input.timeRange : undefined), state: { - originatingApp: '', + originatingApp: options?.originatingApp ?? '', + originatingPath: options?.originatingPath, valueInput: input, }, }); diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/scheduled_query_group_queries_table.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/scheduled_query_group_queries_table.tsx index 36d15587086f2..5b87300850f27 100644 --- a/x-pack/plugins/osquery/public/scheduled_query_groups/scheduled_query_group_queries_table.tsx +++ b/x-pack/plugins/osquery/public/scheduled_query_groups/scheduled_query_group_queries_table.tsx @@ -153,7 +153,7 @@ const ViewResultsInLensActionComponent: React.FC { - const openInNewWindow = !(!isModifiedEvent(event) && isLeftClickEvent(event)); + const openInNewTab = !(!isModifiedEvent(event) && isLeftClickEvent(event)); event.preventDefault(); @@ -167,7 +167,9 @@ const ViewResultsInLensActionComponent: React.FC = ({ children, onAppLeave }) => { - const { application, overlays } = useKibana().services; + const { application, overlays, ...rest } = useKibana().services; const subPluginId = useRef(''); const { ref, height = 0 } = useThrottledResizeObserver(300); const banners$ = overlays.banners.get$(); @@ -57,6 +58,13 @@ const HomePageComponent: React.FC = ({ children, onAppLeave }) => return () => subscription.unsubscribe(); }, [banners$]); // Only un/re-subscribe if the Observable changes + console.error( + 'rest', + rest, + rest.embeddable.getStateTransfer().getIncomingEmbeddablePackage('securitySolution:case', true) + ); + // console.error('navigateToWithEmbeddablePackage', location); + application.currentAppId$.subscribe((appId) => { subPluginId.current = appId ?? ''; }); diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx index 62b1d20dbaef4..ec07b4f8b1c57 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx @@ -216,6 +216,7 @@ export const CaseView = React.memo(({ caseId, subCaseId, userCanCrud }: Props) = }, getCaseDetailHrefWithCommentId, lensIntegration: { + editor_context: lensMarkdownPlugin.context, editor_plugins: { parsingPlugin: lensMarkdownPlugin.parser, processingPluginRenderer: lensMarkdownPlugin.renderer, diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/editor.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/editor.tsx index 12084a17e888a..81c1eace0b83f 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/editor.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/editor.tsx @@ -37,6 +37,8 @@ const MarkdownEditorComponent: React.FC = ({ [] ); + console.error('editorId', editorId); + return ( (null); diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/index.ts b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/index.ts index aa01ac0555c1e..493d960606414 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/index.ts +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/index.ts @@ -8,5 +8,6 @@ import { plugin } from './plugin'; import { LensParser } from './parser'; import { LensMarkDownRenderer } from './processor'; +import { LensContext } from './context'; -export { plugin, LensParser as parser, LensMarkDownRenderer as renderer }; +export { LensContext as context, plugin, LensParser as parser, LensMarkDownRenderer as renderer }; diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx index 1ba7a5542dbeb..18ce588d4ca88 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx @@ -5,11 +5,13 @@ * 2.0. */ +import { find, omit } from 'lodash'; import { EuiComboBox, EuiModalBody, EuiModalHeader, EuiModalHeaderTitle, + EuiMarkdownContext, EuiMarkdownEditorUiPlugin, EuiCodeBlock, EuiSpacer, @@ -22,7 +24,7 @@ import { EuiDatePicker, EuiDatePickerRange, } from '@elastic/eui'; -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useContext, useEffect, useState } from 'react'; import styled from 'styled-components'; import moment, { Moment } from 'moment'; @@ -53,8 +55,10 @@ const LensEditorComponent: React.FC = ({ onClosePopover, onInsert, }) => { + const markdownContext = useContext(EuiMarkdownContext); const soClient = useKibana().services.savedObjects.client; const [lensOptions, setLensOptions] = useState>([]); + const [lensSavedObjects, setLensSavedObjects] = useState([]); const [selectedOptions, setSelectedOptions] = useState>( id && title ? [{ value: id, label: title }] : [] ); @@ -66,6 +70,15 @@ const LensEditorComponent: React.FC = ({ defaultEndDate ? moment(defaultEndDate) : moment() ); + useEffect( + () => + console.error( + 'textarea', + document.querySelector('textarea.euiMarkdownEditorTextArea').value + ), + [] + ); + useEffect(() => { const fetchLensSavedObjects = async () => { const { savedObjects } = await soClient.find({ @@ -78,6 +91,7 @@ const LensEditorComponent: React.FC = ({ })); setLensOptions(options); + setLensSavedObjects(savedObjects.map((so) => omit(so, ['client']))); }; fetchLensSavedObjects(); }, [soClient]); @@ -96,19 +110,22 @@ const LensEditorComponent: React.FC = ({ const handleAdd = useCallback(() => { if (lensSavedObjectId && selectedOptions[0]) { + console.error(find(lensSavedObjects, ['id', lensSavedObjectId])); onInsert( `!{lens${JSON.stringify({ - id: lensSavedObjectId, startDate, endDate, title: selectedOptions[0].label, + attributes: find(lensSavedObjects, ['id', lensSavedObjectId]).attributes, })}}`, { block: true, } ); } - }, [lensSavedObjectId, selectedOptions, onInsert, startDate, endDate]); + }, [lensSavedObjectId, selectedOptions, lensSavedObjects, onInsert, startDate, endDate]); + + console.error('markdownContext', markdownContext); return ( @@ -161,7 +178,7 @@ const LensEditorComponent: React.FC = ({ ), - editor: function editor({ node, onSave, onCancel }) { + editor: function editor({ node, onSave, onCancel, ...rest }) { + console.error('editorr', node); + console.error('ssssssa', rest); return ( = ({ - id, + attributes, title, startDate, endDate, onBrushEnd, }) => { - const kibana = useKibana(); - const LensComponent = kibana?.services?.lens?.EmbeddableComponent!; + const location = useLocation(); + const { + EmbeddableComponent, + navigateToPrefilledEditor, + canUseEditor, + } = useKibana().services.lens; + console.error('loaa', location); + + console.error('sss', attributes, canUseEditor()); return ( - {id ? ( + {attributes ? ( <> - -
{title}
-
+ + + +
{title}
+
+
+ + { + if (attributes) { + navigateToPrefilledEditor( + { + id: '', + timeRange: { + from: startDate ?? 'now-5d', + to: endDate ?? 'now', + mode: startDate ? 'absolute' : 'relative', + }, + attributes: { + ...attributes.attributes, + references: attributes.references, + }, + }, + { + originatingApp: 'securitySolution:case', + originatingPath: `${location.pathname}${location.search}`, + } + ); + } + }} + > + {`Open in Lens`} + + +
+ - + + {attributes ? ( + + ) : null} ) : null} From 039de02ed643521db4dafb716614d5e193864fdb Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Mon, 21 Jun 2021 14:49:25 +0200 Subject: [PATCH 07/41] wip --- .../public/components/add_comment/index.tsx | 7 +- .../components/markdown_editor/editor.tsx | 136 ++++++++++-------- .../components/markdown_editor/eui_form.tsx | 71 +++++---- .../components/user_action_tree/index.tsx | 9 ++ .../markdown_editor/plugins/lens/plugin.tsx | 1 + 5 files changed, 123 insertions(+), 101 deletions(-) diff --git a/x-pack/plugins/cases/public/components/add_comment/index.tsx b/x-pack/plugins/cases/public/components/add_comment/index.tsx index 04104f0b9471d..876416a2170fa 100644 --- a/x-pack/plugins/cases/public/components/add_comment/index.tsx +++ b/x-pack/plugins/cases/public/components/add_comment/index.tsx @@ -6,7 +6,7 @@ */ import { EuiButton, EuiLoadingSpinner } from '@elastic/eui'; -import React, { useCallback, forwardRef, useImperativeHandle } from 'react'; +import React, { useCallback, useRef, forwardRef, useImperativeHandle } from 'react'; import styled from 'styled-components'; import { CommentType } from '../../../common'; @@ -48,6 +48,7 @@ export const AddComment = React.memo( { caseId, disabled, onCommentPosted, onCommentSaving, showLoading = true, subCaseId }, ref ) => { + const editorRef = useRef(); const owner = useOwnerContext(); const { isLoading, postComment } = usePostComment(); @@ -70,6 +71,7 @@ export const AddComment = React.memo( useImperativeHandle(ref, () => ({ addQuote, + editorRef: editorRef.current, })); const onSubmit = useCallback(async () => { @@ -88,6 +90,8 @@ export const AddComment = React.memo( } }, [submit, onCommentSaving, postComment, caseId, owner, onCommentPosted, subCaseId, reset]); + console.error('aaaaaaa', editorRef, ref); + return ( {isLoading && showLoading && } @@ -96,6 +100,7 @@ export const AddComment = React.memo( path={fieldName} component={MarkdownEditorForm} componentProps={{ + ref: editorRef, idAria: 'caseComment', isDisabled: isLoading, dataTestSubj: 'add-comment', diff --git a/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx b/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx index 60ea014bc5427..bbab991075762 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx @@ -5,7 +5,16 @@ * 2.0. */ -import React, { memo, useContext, useEffect, useRef, useState, useCallback } from 'react'; +import React, { + memo, + useContext, + forwardRef, + useEffect, + useRef, + useState, + useCallback, + useImperativeHandle, +} from 'react'; import { PluggableList } from 'unified'; import { EuiMarkdownEditor, EuiMarkdownEditorUiPlugin, EuiMarkdownContext } from '@elastic/eui'; import { usePlugins } from './use_plugins'; @@ -23,46 +32,49 @@ interface MarkdownEditorProps { value: string; } -const MarkdownEditorComponent: React.FC = ({ - ariaLabel, - dataTestSubj, - editorId, - height, - onChange, - value, -}) => { - const [markdownErrorMessages, setMarkdownErrorMessages] = useState([]); +const MarkdownEditorComponent: React.FC = forwardRef( + ({ ariaLabel, dataTestSubj, editorId, height, onChange, value }, ref) => { + const [markdownErrorMessages, setMarkdownErrorMessages] = useState([]); - const { parsingPlugins, processingPlugins, uiPlugins } = usePlugins(); - const LensContextProvider = useLensContext()?.editor_context.Provider; - const editorRef = useRef(null); - const markdownContext = useContext(EuiMarkdownContext); + const { parsingPlugins, processingPlugins, uiPlugins } = usePlugins(); + const LensContextProvider = useLensContext()?.editor_context.Provider; + const editorRef = useRef(null); + const markdownContext = useContext(EuiMarkdownContext); - console.error('markdownContext', markdownContext); - console.error('useRef', editorRef.current); + console.error('markdownContext', markdownContext); + console.error('useRef', ref.current); - const onParse = useCallback( - (err, { messages }) => { - console.error('onParse', markdownContext); - markdownContext.openPluginEditor(uiPlugins[2]); - markdownContext.replaceNode( - { - start: { - offset: 0, + const onParse = useCallback( + (err, { messages }) => { + console.error('onParse', markdownContext); + markdownContext.openPluginEditor(uiPlugins[2]); + markdownContext.replaceNode( + { + start: { + offset: 0, + }, + end: { + offset: 0, + }, }, - end: { - offset: 0, - }, - }, - 'dupa' - ); - setMarkdownErrorMessages(err ? [err] : messages); - }, - [markdownContext, uiPlugins] - ); + 'dupa' + ); + setMarkdownErrorMessages(err ? [err] : messages); + }, + [markdownContext, uiPlugins] + ); + + useImperativeHandle(ref, (payload) => { + console.error('reft', ref, editorRef); + return { + ...editorRef.current, + toolbar: editorRef.current.textarea + .closest('.euiMarkdownEditor') + .querySelector('.euiMarkdownEditorToolbar'), + }; + }); - const editor = ( - <> + const editor = ( = ({ errors={markdownErrorMessages} data-test-subj={dataTestSubj} height={height} - > - <>{`dupa`} - - - ); + /> + ); - // useEffect(() => { - // if (markdownContext) { - // console.error('uiPlugins', uiPlugins[2]); - // markdownContext.openPluginEditor(uiPlugins[2]); - // } - // document.querySelector('textarea.euiMarkdownEditorTextArea')?.focus(); - // }, []); + // useEffect(() => { + // if (markdownContext) { + // console.error('uiPlugins', uiPlugins[2]); + // markdownContext.openPluginEditor(uiPlugins[2]); + // } + // document.querySelector('textarea.euiMarkdownEditorTextArea')?.focus(); + // }, []); - // if (LensContextProvider) { - // return ( - // - // {editor} - // - // ); - // } + // if (LensContextProvider) { + // return ( + // + // {editor} + // + // ); + // } - return editor; -}; + return editor; + } +); export const MarkdownEditor = memo(MarkdownEditorComponent); diff --git a/x-pack/plugins/cases/public/components/markdown_editor/eui_form.tsx b/x-pack/plugins/cases/public/components/markdown_editor/eui_form.tsx index 5b0634302dfb6..630fa8b8e1e66 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/eui_form.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/eui_form.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React from 'react'; +import React, { forwardRef } from 'react'; import styled from 'styled-components'; import { EuiMarkdownEditorProps, EuiFormRow, EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; import { FieldHook, getFieldValidityAndErrorMessage } from '../../common/shared_imports'; @@ -26,40 +26,37 @@ const BottomContentWrapper = styled(EuiFlexGroup)` `} `; -export const MarkdownEditorForm: React.FC = ({ - id, - field, - dataTestSubj, - idAria, - bottomRightContent, -}) => { - const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); +export const MarkdownEditorForm: React.FC = forwardRef( + ({ id, field, dataTestSubj, idAria, bottomRightContent }, ref) => { + const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); - return ( - - <> - - {bottomRightContent && ( - - {bottomRightContent} - - )} - - - ); -}; + return ( + + <> + + {bottomRightContent && ( + + {bottomRightContent} + + )} + + + ); + } +); diff --git a/x-pack/plugins/cases/public/components/user_action_tree/index.tsx b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx index 8c4e69ae0ddd3..f8ba6389bd726 100644 --- a/x-pack/plugins/cases/public/components/user_action_tree/index.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx @@ -587,6 +587,15 @@ export const UserActionTree = React.memo( const comments = [...userActions, ...bottomActions]; + useEffect(() => { + console.error('addCommentRef', addCommentRef.current); + const buttons = addCommentRef.current?.editorRef?.toolbar?.querySelector( + '[aria-label="Insert lens link"]' + ); + buttons.click(); + console.error('buttons', buttons); + }, []); + return ( <> diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx index 18ce588d4ca88..d8a3c6d9852cb 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx @@ -201,6 +201,7 @@ export const plugin: EuiMarkdownEditorUiPlugin = { button: { label: i18n.INSERT_LENS, iconType: 'lensApp', + className: 'markdownLensPlugin', }, helpText: ( From 3c93fff6c4621b8107a47c29f1fd356788096bd8 Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Tue, 22 Jun 2021 22:53:05 +0200 Subject: [PATCH 08/41] WIP --- .../public/components/add_comment/index.tsx | 12 +- .../components/markdown_editor/editor.tsx | 59 +-- .../components/user_action_tree/index.tsx | 37 +- x-pack/plugins/security_solution/kibana.json | 2 +- .../public/app/home/index.tsx | 10 +- .../markdown_editor/plugins/lens/plugin.tsx | 439 +++++++++++++----- .../plugins/lens/processor.tsx | 62 ++- 7 files changed, 418 insertions(+), 203 deletions(-) diff --git a/x-pack/plugins/cases/public/components/add_comment/index.tsx b/x-pack/plugins/cases/public/components/add_comment/index.tsx index 876416a2170fa..07a38f2d31caf 100644 --- a/x-pack/plugins/cases/public/components/add_comment/index.tsx +++ b/x-pack/plugins/cases/public/components/add_comment/index.tsx @@ -19,6 +19,7 @@ import * as i18n from './translations'; import { schema, AddCommentFormSchema } from './schema'; import { InsertTimeline } from '../insert_timeline'; import { useOwnerContext } from '../owner_context/use_owner_context'; + const MySpinner = styled(EuiLoadingSpinner)` position: absolute; top: 50%; @@ -31,6 +32,7 @@ const initialCommentValue: AddCommentFormSchema = { export interface AddCommentRefObject { addQuote: (quote: string) => void; + setComment: (newComment: string) => void; } interface AddCommentProps { @@ -69,8 +71,16 @@ export const AddComment = React.memo( [comment, setFieldValue] ); + const setComment = useCallback( + (newComment) => { + setFieldValue(fieldName, newComment); + }, + [setFieldValue] + ); + useImperativeHandle(ref, () => ({ addQuote, + setComment, editorRef: editorRef.current, })); @@ -90,8 +100,6 @@ export const AddComment = React.memo( } }, [submit, onCommentSaving, postComment, caseId, owner, onCommentPosted, subCaseId, reset]); - console.error('aaaaaaa', editorRef, ref); - return ( {isLoading && showLoading && } diff --git a/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx b/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx index bbab991075762..baaa888935edb 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx @@ -42,29 +42,9 @@ const MarkdownEditorComponent: React.FC = forwardRef( const markdownContext = useContext(EuiMarkdownContext); console.error('markdownContext', markdownContext); - console.error('useRef', ref.current); + console.error('useRef', ref?.current); - const onParse = useCallback( - (err, { messages }) => { - console.error('onParse', markdownContext); - markdownContext.openPluginEditor(uiPlugins[2]); - markdownContext.replaceNode( - { - start: { - offset: 0, - }, - end: { - offset: 0, - }, - }, - 'dupa' - ); - setMarkdownErrorMessages(err ? [err] : messages); - }, - [markdownContext, uiPlugins] - ); - - useImperativeHandle(ref, (payload) => { + useImperativeHandle(ref, () => { console.error('reft', ref, editorRef); return { ...editorRef.current, @@ -84,34 +64,25 @@ const MarkdownEditorComponent: React.FC = forwardRef( uiPlugins={uiPlugins} parsingPluginList={parsingPlugins} processingPluginList={processingPlugins} - onParse={onParse} errors={markdownErrorMessages} data-test-subj={dataTestSubj} height={height} /> ); - // useEffect(() => { - // if (markdownContext) { - // console.error('uiPlugins', uiPlugins[2]); - // markdownContext.openPluginEditor(uiPlugins[2]); - // } - // document.querySelector('textarea.euiMarkdownEditorTextArea')?.focus(); - // }, []); - - // if (LensContextProvider) { - // return ( - // - // {editor} - // - // ); - // } + if (LensContextProvider) { + return ( + + {editor} + + ); + } return editor; } diff --git a/x-pack/plugins/cases/public/components/user_action_tree/index.tsx b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx index f8ba6389bd726..1b90468254f18 100644 --- a/x-pack/plugins/cases/public/components/user_action_tree/index.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx @@ -22,7 +22,7 @@ import { isRight } from 'fp-ts/Either'; import * as i18n from './translations'; import { useUpdateComment } from '../../containers/use_update_comment'; -import { useCurrentUser } from '../../common/lib/kibana'; +import { useCurrentUser, useKibana } from '../../common/lib/kibana'; import { AddComment, AddCommentRefObject } from '../add_comment'; import { ActionConnector, @@ -134,6 +134,7 @@ export const UserActionTree = React.memo( useFetchAlertData, userCanCrud, }: UserActionTreeProps) => { + const { embeddable, storage } = useKibana().services; const { detailName: caseId, commentId, subCaseId } = useParams<{ detailName: string; commentId?: string; @@ -588,12 +589,34 @@ export const UserActionTree = React.memo( const comments = [...userActions, ...bottomActions]; useEffect(() => { - console.error('addCommentRef', addCommentRef.current); - const buttons = addCommentRef.current?.editorRef?.toolbar?.querySelector( - '[aria-label="Insert lens link"]' - ); - buttons.click(); - console.error('buttons', buttons); + console.error('commentDraft', storage.get('xpack.cases.commentDraft')); + const incomingEmbeddablePackage = embeddable + .getStateTransfer() + .getIncomingEmbeddablePackage('securitySolution:case'); + let draftComment; + if (storage.get('xpack.cases.commentDraft')) { + try { + draftComment = JSON.parse(storage.get('xpack.cases.commentDraft')); + } catch (e) {} + } + + if (incomingEmbeddablePackage) { + console.error('incomingEmbeddablePackage', incomingEmbeddablePackage); + + if (draftComment) { + if (!draftComment.commentId) { + addCommentRef.current?.setComment(draftComment.comment); + const buttons = addCommentRef.current?.editorRef?.toolbar?.querySelector( + '[aria-label="Insert lens link"]' + ); + buttons.click(); + } + + if (draftComment.commentId) { + setManangeMarkdownEditIds([draftComment.commentId]); + } + } + } }, []); return ( diff --git a/x-pack/plugins/security_solution/kibana.json b/x-pack/plugins/security_solution/kibana.json index 08db17d115d1f..e2b22a25517ca 100644 --- a/x-pack/plugins/security_solution/kibana.json +++ b/x-pack/plugins/security_solution/kibana.json @@ -36,5 +36,5 @@ ], "server": true, "ui": true, - "requiredBundles": ["esUiShared", "fleet", "kibanaUtils", "kibanaReact", "lists", "ml"] + "requiredBundles": ["esUiShared", "fleet", "kibanaUtils", "kibanaReact", "lists", "ml", "savedObjects"] } diff --git a/x-pack/plugins/security_solution/public/app/home/index.tsx b/x-pack/plugins/security_solution/public/app/home/index.tsx index e576d9e362202..1b0ddcfb9ae7d 100644 --- a/x-pack/plugins/security_solution/public/app/home/index.tsx +++ b/x-pack/plugins/security_solution/public/app/home/index.tsx @@ -7,7 +7,6 @@ import React, { useEffect, useRef, useState } from 'react'; import styled from 'styled-components'; -import { useLocation } from 'react-router-dom'; import { TimelineId } from '../../../common/types/timeline'; import { DragDropContextWrapper } from '../../common/components/drag_and_drop/drag_drop_context_wrapper'; @@ -46,7 +45,7 @@ interface HomePageProps { } const HomePageComponent: React.FC = ({ children, onAppLeave }) => { - const { application, overlays, ...rest } = useKibana().services; + const { application, overlays } = useKibana().services; const subPluginId = useRef(''); const { ref, height = 0 } = useThrottledResizeObserver(300); const banners$ = overlays.banners.get$(); @@ -58,13 +57,6 @@ const HomePageComponent: React.FC = ({ children, onAppLeave }) => return () => subscription.unsubscribe(); }, [banners$]); // Only un/re-subscribe if the Observable changes - console.error( - 'rest', - rest, - rest.embeddable.getStateTransfer().getIncomingEmbeddablePackage('securitySolution:case', true) - ); - // console.error('navigateToWithEmbeddablePackage', location); - application.currentAppId$.subscribe((appId) => { subPluginId.current = appId ?? ''; }); diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx index d8a3c6d9852cb..83bb872f30bb2 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx @@ -5,13 +5,16 @@ * 2.0. */ +import dateMath from '@elastic/datemath'; + import { find, omit } from 'lodash'; import { + EuiFieldText, + EuiModal, EuiComboBox, EuiModalBody, EuiModalHeader, EuiModalHeaderTitle, - EuiMarkdownContext, EuiMarkdownEditorUiPlugin, EuiCodeBlock, EuiSpacer, @@ -23,21 +26,102 @@ import { EuiFormRow, EuiDatePicker, EuiDatePickerRange, + EuiFlyoutBody, } from '@elastic/eui'; -import React, { useCallback, useContext, useEffect, useState } from 'react'; +import { IndexPattern } from 'src/plugins/data/public'; +import React, { useCallback, useContext, useMemo, useRef, useEffect, useState } from 'react'; import styled from 'styled-components'; import moment, { Moment } from 'moment'; +import { useLocation } from 'react-router-dom'; -import { TypedLensByValueInput } from '../../../../../../../lens/public'; -import { useKibana } from '../../../../lib/kibana'; +import { SavedObjectFinderUi } from '../../../../../../../../../src/plugins/saved_objects/public'; +import { + TypedLensByValueInput, + PersistedIndexPatternLayer, + XYState, + LensEmbeddableInput, +} from '../../../../../../../lens/public'; +import { toMountPoint, createKibanaReactContext, useKibana } from '../../../../lib/kibana'; import { LensMarkDownRenderer } from './processor'; import { ID } from './constants'; import * as i18n from './translations'; +import { LensContext } from './context'; const ModalContainer = styled.div` width: ${({ theme }) => theme.eui.euiBreakpoints.m}; + min-height: 500px; `; +// Generate a Lens state based on some app-specific input parameters. +// `TypedLensByValueInput` can be used for type-safety - it uses the same interfaces as Lens-internal code. +function getLensAttributes(defaultIndexPattern: IndexPattern): TypedLensByValueInput['attributes'] { + const dataLayer: PersistedIndexPatternLayer = { + columnOrder: ['col1', 'col2'], + columns: { + col2: { + dataType: 'number', + isBucketed: false, + label: 'Count of records', + operationType: 'count', + scale: 'ratio', + sourceField: 'Records', + }, + col1: { + dataType: 'date', + isBucketed: true, + label: '@timestamp', + operationType: 'date_histogram', + params: { interval: 'auto' }, + scale: 'interval', + sourceField: defaultIndexPattern.timeFieldName!, + }, + }, + }; + + const xyConfig: XYState = { + axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true }, + fittingFunction: 'None', + gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true }, + layers: [ + { + accessors: ['col2'], + layerId: 'layer1', + seriesType: 'bar_stacked', + xAccessor: 'col1', + yConfig: [{ forAccessor: 'col2', color: 'red' }], + }, + ], + legend: { isVisible: true, position: 'right' }, + preferredSeriesType: 'bar_stacked', + tickLabelsVisibilitySettings: { x: true, yLeft: true, yRight: true }, + valueLabels: 'hide', + }; + + return { + visualizationType: 'lnsXY', + title: 'Prefilled from example app', + references: [ + { + id: defaultIndexPattern.id!, + name: 'indexpattern-datasource-current-indexpattern', + type: 'index-pattern', + }, + ], + state: { + datasourceStates: { + indexpattern: { + layers: { + layer1: dataLayer, + }, + }, + }, + filters: [], + query: { language: 'kuery', query: '' }, + visualization: xyConfig, + }, + }; +} + interface LensEditorProps { id?: string | null; title?: string | null; @@ -48,6 +132,7 @@ interface LensEditorProps { } const LensEditorComponent: React.FC = ({ + editMode, id, title, startDate: defaultStartDate, @@ -55,51 +140,23 @@ const LensEditorComponent: React.FC = ({ onClosePopover, onInsert, }) => { - const markdownContext = useContext(EuiMarkdownContext); - const soClient = useKibana().services.savedObjects.client; - const [lensOptions, setLensOptions] = useState>([]); - const [lensSavedObjects, setLensSavedObjects] = useState([]); - const [selectedOptions, setSelectedOptions] = useState>( - id && title ? [{ value: id, label: title }] : [] - ); - const [lensSavedObjectId, setLensSavedObjectId] = useState(id ?? null); + const location = useLocation(); + const { embeddable, savedObjects, uiSettings, lens, storage, ...rest } = useKibana().services; + + const [lensEmbeddableAttributes, setLensEmbeddableAttributes] = useState(null); const [startDate, setStartDate] = useState( defaultStartDate ? moment(defaultStartDate) : moment().subtract(7, 'd') ); const [endDate, setEndDate] = useState( defaultEndDate ? moment(defaultEndDate) : moment() ); + const [lensTitle, setLensTitle] = useState(title ?? ''); + const [showLensSavedObjectsModal, setShowLensSavedObjectsModal] = useState(false); + const context = useContext(LensContext); - useEffect( - () => - console.error( - 'textarea', - document.querySelector('textarea.euiMarkdownEditorTextArea').value - ), - [] - ); + console.error('contextcontextcontext', context); - useEffect(() => { - const fetchLensSavedObjects = async () => { - const { savedObjects } = await soClient.find({ - type: 'lens', - perPage: 1000, - }); - const options = savedObjects.map((lensSO) => ({ - label: lensSO.attributes.title, - value: lensSO.id, - })); - - setLensOptions(options); - setLensSavedObjects(savedObjects.map((so) => omit(so, ['client']))); - }; - fetchLensSavedObjects(); - }, [soClient]); - - const handleChange = useCallback((options) => { - setSelectedOptions(options); - setLensSavedObjectId(options[0] ? options[0].value : null); - }, []); + console.error('rest', rest.data.query.timefilter.timefilter.getTime(), rest); const handleLensDateChange = useCallback((data) => { if (data.range?.length === 2) { @@ -108,89 +165,255 @@ const LensEditorComponent: React.FC = ({ } }, []); + const handleTitleChange = useCallback((e) => { + setLensTitle(e.target.value); + }, []); + const handleAdd = useCallback(() => { - if (lensSavedObjectId && selectedOptions[0]) { - console.error(find(lensSavedObjects, ['id', lensSavedObjectId])); + if (lensEmbeddableAttributes) { onInsert( `!{lens${JSON.stringify({ startDate, endDate, - title: selectedOptions[0].label, - attributes: find(lensSavedObjects, ['id', lensSavedObjectId]).attributes, + title: lensTitle, + attributes: lensEmbeddableAttributes, })}}`, { block: true, } ); } - }, [lensSavedObjectId, selectedOptions, lensSavedObjects, onInsert, startDate, endDate]); + }, [lensEmbeddableAttributes, onInsert, startDate, endDate, lensTitle]); + + const originatingPath = useMemo(() => { + const commentId = context?.editorId; + + if (!commentId) { + return `${location.pathname}${location.search}`; + } - console.error('markdownContext', markdownContext); + return `${location.pathname}/${commentId}${location.search}`; + }, [context?.editorId, location.pathname, location.search]); + + const handleEditInLensClick = useCallback(() => { + console.error('handleEditInLensClick', location); + lens.navigateToPrefilledEditor( + { + id: '', + timeRange: { + from: lensEmbeddableAttributes ? startDate : 'now-5d', + to: lensEmbeddableAttributes ? endDate : 'now', + mode: startDate ? 'absolute' : 'relative', + }, + attributes: + lensEmbeddableAttributes ?? + getLensAttributes({ + id: 'logs-*', + name: 'indexpattern-datasource-current-indexpattern', + type: 'index-pattern', + }), + }, + { + originatingApp: 'securitySolution:case', + originatingPath, + } + ); + + storage.set( + 'xpack.cases.commentDraft', + JSON.stringify({ + commentId: context?.editorId, + comment: context?.value, + }) + ); + }, [ + context.editorId, + context.value, + endDate, + originatingPath, + lens, + lensEmbeddableAttributes, + location, + startDate, + storage, + ]); + + useEffect(() => { + const incomingEmbeddablePackage = embeddable + .getStateTransfer() + .getIncomingEmbeddablePackage('securitySolution:case', true); + + if ( + incomingEmbeddablePackage?.type === 'lens' && + incomingEmbeddablePackage?.input?.attributes + ) { + setLensEmbeddableAttributes(incomingEmbeddablePackage?.input.attributes); + const lensTime = rest.data.query.timefilter.timefilter.getTime(); + + if (lensTime) { + setStartDate(dateMath.parse(lensTime.from)); + setEndDate(dateMath.parse(lensTime.to)); + } + } + + console.error('stoargesgeet', storage.get('xpack.cases.commentDraft')); + }, [embeddable, storage]); + + console.error('lensEmbeddableAttributes', lensEmbeddableAttributes); return ( - - - - {id && title ? 'Edit Lens visualization' : 'Add Lens visualization'} - - - - - - - - - - - - endDate : false} - aria-label="Start date" - showTimeSelect - /> - } - endDateControl={ - endDate : false} - aria-label="End date" - showTimeSelect + <> + + + + {editMode ? 'Edit Lens visualization' : 'Add Lens visualization'} + + + + + + + Create visualization + + + + setShowLensSavedObjectsModal(true)} + > + Add from library + + + + {lensEmbeddableAttributes ? ( + + + + + + + {/* + + endDate : false} + aria-label="Start date" + showTimeSelect + /> + } + endDateControl={ + endDate : false} + aria-label="End date" + showTimeSelect + /> + } + /> + + */} + + + {`Edit in Lens`} + + + + ) : null} + + + + + {'Cancel'} + + {'Add to a Case'} + + + + {showLensSavedObjectsModal ? ( + setShowLensSavedObjectsModal(false)}> + + + +

Modal title

+
+
+ + + { + console.error('apya', savedObjectId, savedObjectType, fullName, savedObject); + setLensEmbeddableAttributes({ + ...savedObject.attributes, + references: savedObject.references, + }); + setShowLensSavedObjectsModal(false); + }} + showFilter + noItemsMessage={ + 'No matching lens found.' + + // i18n.translate( + // 'xpack.transform.newTransform.searchSelection.notFoundLabel', + // { + // defaultMessage: 'No matching lens found.', + // } + // ) } + savedObjectMetaData={[ + { + type: 'lens', + getIconForSavedObject: () => 'lensApp', + name: 'Lens', + includeFields: ['*'], + // i18n.translate( + // 'xpack.transform.newTransform.searchSelection.savedObjectType.search', + // { + // defaultMessage: 'Lens', + // } + // ), + }, + ]} + fixedPageSize={10} + uiSettings={uiSettings} + savedObjects={savedObjects} /> -
-
-
- - -
- - {'Cancel'} - - {'Add to a Case'} - - -
+ +
+ + ) : null} + ); }; @@ -201,7 +424,6 @@ export const plugin: EuiMarkdownEditorUiPlugin = { button: { label: i18n.INSERT_LENS, iconType: 'lensApp', - className: 'markdownLensPlugin', }, helpText: ( @@ -213,6 +435,7 @@ export const plugin: EuiMarkdownEditorUiPlugin = { console.error('ssssssa', rest); return ( = ({ @@ -41,6 +42,7 @@ const LensMarkDownRendererComponent: React.FC = ({ startDate, endDate, onBrushEnd, + viewMode = true, }) => { const location = useLocation(); const { @@ -63,35 +65,34 @@ const LensMarkDownRendererComponent: React.FC = ({ - { - if (attributes) { - navigateToPrefilledEditor( - { - id: '', - timeRange: { - from: startDate ?? 'now-5d', - to: endDate ?? 'now', - mode: startDate ? 'absolute' : 'relative', + {viewMode ? ( + { + if (attributes) { + navigateToPrefilledEditor( + { + id: '', + timeRange: { + from: startDate ?? 'now-5d', + to: endDate ?? 'now', + mode: startDate ? 'absolute' : 'relative', + }, + attributes, }, - attributes: { - ...attributes.attributes, - references: attributes.references, - }, - }, - { - originatingApp: 'securitySolution:case', - originatingPath: `${location.pathname}${location.search}`, - } - ); - } - }} - > - {`Open in Lens`} - + { + originatingApp: 'securitySolution:case', + originatingPath: `${location.pathname}${location.search}`, + } + ); + } + }} + > + {`Open in Lens`} + + ) : null} @@ -106,10 +107,7 @@ const LensMarkDownRendererComponent: React.FC = ({ to: endDate ?? 'now', mode: startDate ? 'absolute' : 'relative', }} - attributes={{ - ...attributes.attributes, - references: attributes.references, - }} + attributes={attributes} onBrushEnd={onBrushEnd} /> ) : null} From c706c189b16201a49cc777c14e8910424d9cc26d Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Thu, 24 Jun 2021 14:43:14 +0200 Subject: [PATCH 09/41] cleanup --- .../public/lib/state_transfer/embeddable_state_transfer.ts | 1 - x-pack/plugins/lens/public/app_plugin/mounter.tsx | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.ts b/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.ts index a8f34d609e82a..26d366bb8dabe 100644 --- a/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.ts +++ b/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.ts @@ -174,7 +174,6 @@ export class EmbeddableStateTransfer { }, }; this.storage.set(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, stateObject); - console.error('navigateToWithState', appId, options, this.storage); await this.navigateToApp(appId, { path: options?.path, openInNewTab: options?.openInNewTab }); } } diff --git a/x-pack/plugins/lens/public/app_plugin/mounter.tsx b/x-pack/plugins/lens/public/app_plugin/mounter.tsx index 039fbf53e07b4..25898ae2eb86e 100644 --- a/x-pack/plugins/lens/public/app_plugin/mounter.tsx +++ b/x-pack/plugins/lens/public/app_plugin/mounter.tsx @@ -153,7 +153,9 @@ export async function mountApp( }, }); } else { - coreStart.application.navigateToApp(embeddableEditorIncomingState?.originatingApp); + coreStart.application.navigateToApp(embeddableEditorIncomingState?.originatingApp, { + path: embeddableEditorIncomingState?.originatingPath, + }); } }; const initialContext = From bbcbacda6c57060b652626e86ed87ea9146636fd Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Thu, 24 Jun 2021 22:21:21 +0200 Subject: [PATCH 10/41] WIP --- .../components/markdown_editor/editor.tsx | 15 +- .../markdown_editor/plugins/lens/plugin.tsx | 179 ++++++++---------- 2 files changed, 81 insertions(+), 113 deletions(-) diff --git a/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx b/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx index baaa888935edb..879cf466614ea 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx @@ -5,18 +5,9 @@ * 2.0. */ -import React, { - memo, - useContext, - forwardRef, - useEffect, - useRef, - useState, - useCallback, - useImperativeHandle, -} from 'react'; +import React, { memo, forwardRef, useRef, useState, useImperativeHandle } from 'react'; import { PluggableList } from 'unified'; -import { EuiMarkdownEditor, EuiMarkdownEditorUiPlugin, EuiMarkdownContext } from '@elastic/eui'; +import { EuiMarkdownEditor, EuiMarkdownEditorUiPlugin } from '@elastic/eui'; import { usePlugins } from './use_plugins'; import { useLensContext } from '../lens_context/use_lens_context'; @@ -39,9 +30,7 @@ const MarkdownEditorComponent: React.FC = forwardRef( const { parsingPlugins, processingPlugins, uiPlugins } = usePlugins(); const LensContextProvider = useLensContext()?.editor_context.Provider; const editorRef = useRef(null); - const markdownContext = useContext(EuiMarkdownContext); - console.error('markdownContext', markdownContext); console.error('useRef', ref?.current); useImperativeHandle(ref, () => { diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx index 83bb872f30bb2..f5eda1a4fd6e8 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx @@ -7,15 +7,14 @@ import dateMath from '@elastic/datemath'; -import { find, omit } from 'lodash'; import { EuiFieldText, EuiModal, - EuiComboBox, EuiModalBody, EuiModalHeader, EuiModalHeaderTitle, EuiMarkdownEditorUiPlugin, + EuiMarkdownContext, EuiCodeBlock, EuiSpacer, EuiModalFooter, @@ -24,10 +23,8 @@ import { EuiFlexItem, EuiFlexGroup, EuiFormRow, - EuiDatePicker, - EuiDatePickerRange, - EuiFlyoutBody, } from '@elastic/eui'; +import { insertText } from '@elastic/eui/lib/components/markdown_editor/markdown_actions'; import { IndexPattern } from 'src/plugins/data/public'; import React, { useCallback, useContext, useMemo, useRef, useEffect, useState } from 'react'; import styled from 'styled-components'; @@ -39,9 +36,8 @@ import { TypedLensByValueInput, PersistedIndexPatternLayer, XYState, - LensEmbeddableInput, } from '../../../../../../../lens/public'; -import { toMountPoint, createKibanaReactContext, useKibana } from '../../../../lib/kibana'; +import { useKibana } from '../../../../lib/kibana'; import { LensMarkDownRenderer } from './processor'; import { ID } from './constants'; import * as i18n from './translations'; @@ -49,7 +45,10 @@ import { LensContext } from './context'; const ModalContainer = styled.div` width: ${({ theme }) => theme.eui.euiBreakpoints.m}; - min-height: 500px; + + .euiModalBody { + min-height: 300px; + } `; // Generate a Lens state based on some app-specific input parameters. @@ -88,7 +87,7 @@ function getLensAttributes(defaultIndexPattern: IndexPattern): TypedLensByValueI layerId: 'layer1', seriesType: 'bar_stacked', xAccessor: 'col1', - yConfig: [{ forAccessor: 'col2', color: 'red' }], + yConfig: [{ forAccessor: 'col2', color: undefined }], }, ], legend: { isVisible: true, position: 'right' }, @@ -99,13 +98,18 @@ function getLensAttributes(defaultIndexPattern: IndexPattern): TypedLensByValueI return { visualizationType: 'lnsXY', - title: 'Prefilled from example app', + title: '', references: [ { id: defaultIndexPattern.id!, name: 'indexpattern-datasource-current-indexpattern', type: 'index-pattern', }, + { + id: defaultIndexPattern.id!, + name: 'indexpattern-datasource-layer-layer1', + type: 'index-pattern', + }, ], state: { datasourceStates: { @@ -123,40 +127,49 @@ function getLensAttributes(defaultIndexPattern: IndexPattern): TypedLensByValueI } interface LensEditorProps { + editMode?: boolean | null; id?: string | null; title?: string | null; startDate?: Moment | null; endDate?: Moment | null; onClosePopover: () => void; - onInsert: (markdown: string, config: { block: boolean }) => void; + onSave: (markdown: string, config: { block: boolean }) => void; } -const LensEditorComponent: React.FC = ({ - editMode, - id, - title, - startDate: defaultStartDate, - endDate: defaultEndDate, - onClosePopover, - onInsert, -}) => { +const LensEditorComponent: React.FC = ({ node, onClosePopover, onSave }) => { const location = useLocation(); - const { embeddable, savedObjects, uiSettings, lens, storage, ...rest } = useKibana().services; + const { + embeddable, + savedObjects, + uiSettings, + lens, + storage, + data: { + indexPatterns, + query: { + timefilter: { timefilter }, + }, + }, + ...rest + } = useKibana().services; - const [lensEmbeddableAttributes, setLensEmbeddableAttributes] = useState(null); + const [lensEmbeddableAttributes, setLensEmbeddableAttributes] = useState( + node?.attributes ?? null + ); const [startDate, setStartDate] = useState( - defaultStartDate ? moment(defaultStartDate) : moment().subtract(7, 'd') + node?.startDate ? moment(node.startDate) : moment().subtract(7, 'd') ); const [endDate, setEndDate] = useState( - defaultEndDate ? moment(defaultEndDate) : moment() + node?.endDate ? moment(node.endDate) : moment() ); - const [lensTitle, setLensTitle] = useState(title ?? ''); + const [lensTitle, setLensTitle] = useState(node?.title ?? ''); const [showLensSavedObjectsModal, setShowLensSavedObjectsModal] = useState(false); const context = useContext(LensContext); + const markdownContext = useContext(EuiMarkdownContext); console.error('contextcontextcontext', context); - console.error('rest', rest.data.query.timefilter.timefilter.getTime(), rest); + console.error('rest', timefilter.getTime(), rest); const handleLensDateChange = useCallback((data) => { if (data.range?.length === 2) { @@ -170,8 +183,12 @@ const LensEditorComponent: React.FC = ({ }, []); const handleAdd = useCallback(() => { + if (node) { + return; + } + if (lensEmbeddableAttributes) { - onInsert( + onSave( `!{lens${JSON.stringify({ startDate, endDate, @@ -183,7 +200,7 @@ const LensEditorComponent: React.FC = ({ } ); } - }, [lensEmbeddableAttributes, onInsert, startDate, endDate, lensTitle]); + }, [lensEmbeddableAttributes, onSave, startDate, endDate, lensTitle]); const originatingPath = useMemo(() => { const commentId = context?.editorId; @@ -195,9 +212,18 @@ const LensEditorComponent: React.FC = ({ return `${location.pathname}/${commentId}${location.search}`; }, [context?.editorId, location.pathname, location.search]); - const handleEditInLensClick = useCallback(() => { - console.error('handleEditInLensClick', location); - lens.navigateToPrefilledEditor( + const handleEditInLensClick = useCallback(async () => { + storage.set( + 'xpack.cases.commentDraft', + JSON.stringify({ + commentId: context?.editorId, + comment: context?.value, + position: node?.position, + title: lensTitle, + }) + ); + + lens?.navigateToPrefilledEditor( { id: '', timeRange: { @@ -205,37 +231,25 @@ const LensEditorComponent: React.FC = ({ to: lensEmbeddableAttributes ? endDate : 'now', mode: startDate ? 'absolute' : 'relative', }, - attributes: - lensEmbeddableAttributes ?? - getLensAttributes({ - id: 'logs-*', - name: 'indexpattern-datasource-current-indexpattern', - type: 'index-pattern', - }), + attributes: lensEmbeddableAttributes ?? getLensAttributes(await indexPatterns.getDefault()), }, { originatingApp: 'securitySolution:case', originatingPath, } ); - - storage.set( - 'xpack.cases.commentDraft', - JSON.stringify({ - commentId: context?.editorId, - comment: context?.value, - }) - ); }, [ - context.editorId, - context.value, - endDate, - originatingPath, + storage, + context?.editorId, + context?.value, + node?.position, + lensTitle, lens, lensEmbeddableAttributes, - location, startDate, - storage, + endDate, + indexPatterns, + originatingPath, ]); useEffect(() => { @@ -248,17 +262,20 @@ const LensEditorComponent: React.FC = ({ incomingEmbeddablePackage?.input?.attributes ) { setLensEmbeddableAttributes(incomingEmbeddablePackage?.input.attributes); - const lensTime = rest.data.query.timefilter.timefilter.getTime(); - - if (lensTime) { - setStartDate(dateMath.parse(lensTime.from)); - setEndDate(dateMath.parse(lensTime.to)); + const lensTime = timefilter.getTime(); + if (lensTime?.from && lensTime?.to) { + setStartDate(dateMath.parse(lensTime.from!)); + setEndDate(dateMath.parse(lensTime.to!)); } } console.error('stoargesgeet', storage.get('xpack.cases.commentDraft')); }, [embeddable, storage]); + console.error('insertText', insertText); + + console.error('markdownContext', markdownContext); + console.error('lensEmbeddableAttributes', lensEmbeddableAttributes); return ( @@ -266,7 +283,7 @@ const LensEditorComponent: React.FC = ({ - {editMode ? 'Edit Lens visualization' : 'Add Lens visualization'} + {node ? 'Edit Lens visualization' : 'Add Lens visualization'} @@ -279,7 +296,7 @@ const LensEditorComponent: React.FC = ({ iconType="lensApp" fill > - Create visualization + {'Create visualization'} @@ -289,7 +306,7 @@ const LensEditorComponent: React.FC = ({ iconType="folderOpen" onClick={() => setShowLensSavedObjectsModal(true)} > - Add from library + {'Add from library'} @@ -304,34 +321,6 @@ const LensEditorComponent: React.FC = ({ /> - {/* - - endDate : false} - aria-label="Start date" - showTimeSelect - /> - } - endDateControl={ - endDate : false} - aria-label="End date" - showTimeSelect - /> - } - /> - - */} = ({ -

Modal title

+

{'Modal title'}

@@ -433,16 +422,6 @@ export const plugin: EuiMarkdownEditorUiPlugin = { editor: function editor({ node, onSave, onCancel, ...rest }) { console.error('editorr', node); console.error('ssssssa', rest); - return ( - - ); + return ; }, }; From 22275e125936b14b892ba8c1ceea105a51a0edc6 Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Fri, 25 Jun 2021 22:58:10 +0200 Subject: [PATCH 11/41] WIP --- .../public/components/add_comment/index.tsx | 2 +- .../public/components/lens_context/index.tsx | 1 + .../lens_context/use_lens_context.ts | 2 +- .../components/markdown_editor/editor.tsx | 22 +- .../components/markdown_editor/use_plugins.ts | 4 +- .../components/user_action_tree/index.tsx | 552 ++++++++---------- .../user_action_tree/user_action_markdown.tsx | 169 +++--- .../app_plugin/save_modal_container.tsx | 1 - .../components/markdown_editor/editor.tsx | 2 - .../markdown_editor/plugins/lens/context.tsx | 2 +- .../markdown_editor/plugins/lens/helpers.ts | 90 +++ .../markdown_editor/plugins/lens/index.ts | 9 +- .../plugins/lens/lens_saved_objects_modal.tsx | 66 +++ .../plugins/lens/modal_container.tsx | 16 + .../markdown_editor/plugins/lens/plugin.tsx | 235 +++----- 15 files changed, 613 insertions(+), 560 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/helpers.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/modal_container.tsx diff --git a/x-pack/plugins/cases/public/components/add_comment/index.tsx b/x-pack/plugins/cases/public/components/add_comment/index.tsx index 18e73ebf940ab..a38ac1918e227 100644 --- a/x-pack/plugins/cases/public/components/add_comment/index.tsx +++ b/x-pack/plugins/cases/public/components/add_comment/index.tsx @@ -81,7 +81,7 @@ export const AddComment = React.memo( useImperativeHandle(ref, () => ({ addQuote, setComment, - editorRef: editorRef.current, + editor: editorRef.current, })); const onSubmit = useCallback(async () => { diff --git a/x-pack/plugins/cases/public/components/lens_context/index.tsx b/x-pack/plugins/cases/public/components/lens_context/index.tsx index 311118d5f8724..029d77208ec5a 100644 --- a/x-pack/plugins/cases/public/components/lens_context/index.tsx +++ b/x-pack/plugins/cases/public/components/lens_context/index.tsx @@ -18,6 +18,7 @@ interface LensProcessingPluginRendererProps { } export interface CasesLensIntegration { + editor_context: any; editor_plugins: { parsingPlugin: Plugin; processingPluginRenderer: React.FC< diff --git a/x-pack/plugins/cases/public/components/lens_context/use_lens_context.ts b/x-pack/plugins/cases/public/components/lens_context/use_lens_context.ts index 8f3c3d4b67d60..4ff8128b16ee3 100644 --- a/x-pack/plugins/cases/public/components/lens_context/use_lens_context.ts +++ b/x-pack/plugins/cases/public/components/lens_context/use_lens_context.ts @@ -8,6 +8,6 @@ import { useContext } from 'react'; import { CasesLensIntegrationContext } from '.'; -export const useLensContext = () => { +export const useCasesLensIntegrationContext = () => { return useContext(CasesLensIntegrationContext); }; diff --git a/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx b/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx index 879cf466614ea..a8cf7aea0e884 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx @@ -5,11 +5,11 @@ * 2.0. */ -import React, { memo, forwardRef, useRef, useState, useImperativeHandle } from 'react'; +import React, { memo, forwardRef, useCallback, useRef, useState, useImperativeHandle } from 'react'; import { PluggableList } from 'unified'; import { EuiMarkdownEditor, EuiMarkdownEditorUiPlugin } from '@elastic/eui'; import { usePlugins } from './use_plugins'; -import { useLensContext } from '../lens_context/use_lens_context'; +import { useCasesLensIntegrationContext } from '../lens_context/use_lens_context'; interface MarkdownEditorProps { ariaLabel: string; @@ -23,12 +23,15 @@ interface MarkdownEditorProps { value: string; } -const MarkdownEditorComponent: React.FC = forwardRef( - ({ ariaLabel, dataTestSubj, editorId, height, onChange, value }, ref) => { +const MarkdownEditorComponent = forwardRef( + ({ ariaLabel, dataTestSubj, editorId, height, onChange, value }: MarkdownEditorProps, ref) => { const [markdownErrorMessages, setMarkdownErrorMessages] = useState([]); - + const onParse = useCallback((err, { messages }) => { + setMarkdownErrorMessages(err ? [err] : messages); + }, []); const { parsingPlugins, processingPlugins, uiPlugins } = usePlugins(); - const LensContextProvider = useLensContext()?.editor_context.Provider; + const CasesLensIntegrationContextProvider = useCasesLensIntegrationContext()?.editor_context + .Provider; const editorRef = useRef(null); console.error('useRef', ref?.current); @@ -53,15 +56,16 @@ const MarkdownEditorComponent: React.FC = forwardRef( uiPlugins={uiPlugins} parsingPluginList={parsingPlugins} processingPluginList={processingPlugins} + onParse={onParse} errors={markdownErrorMessages} data-test-subj={dataTestSubj} height={height} /> ); - if (LensContextProvider) { + if (CasesLensIntegrationContextProvider) { return ( - = forwardRef( }} > {editor} - + ); } diff --git a/x-pack/plugins/cases/public/components/markdown_editor/use_plugins.ts b/x-pack/plugins/cases/public/components/markdown_editor/use_plugins.ts index 02c55b5111ab7..0a6deb1fab7ec 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/use_plugins.ts +++ b/x-pack/plugins/cases/public/components/markdown_editor/use_plugins.ts @@ -13,11 +13,11 @@ import { import { useMemo } from 'react'; import { useTimelineContext } from '../timeline_context/use_timeline_context'; import { TemporaryProcessingPluginsType } from './types'; -import { useLensContext } from '../lens_context/use_lens_context'; +import { useCasesLensIntegrationContext } from '../lens_context/use_lens_context'; export const usePlugins = () => { const timelinePlugins = useTimelineContext()?.editor_plugins; - const lensPlugins = useLensContext()?.editor_plugins; + const lensPlugins = useCasesLensIntegrationContext()?.editor_plugins; return useMemo(() => { const uiPlugins = getDefaultEuiMarkdownUiPlugins(); diff --git a/x-pack/plugins/cases/public/components/user_action_tree/index.tsx b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx index 5be850865f3d0..253304da6de90 100644 --- a/x-pack/plugins/cases/public/components/user_action_tree/index.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx @@ -112,7 +112,6 @@ const MyEuiCommentList = styled(EuiCommentList)` `} `; -const DESCRIPTION_ID = 'description'; const NEW_ID = 'newComment'; export const UserActionTree = React.memo( @@ -134,6 +133,7 @@ export const UserActionTree = React.memo( useFetchAlertData, userCanCrud, }: UserActionTreeProps) => { + const commentRefs = useRef>({}); const { embeddable, storage } = useKibana().services; const { detailName: caseId, commentId, subCaseId } = useParams<{ detailName: string; @@ -141,12 +141,11 @@ export const UserActionTree = React.memo( subCaseId?: string; }>(); const handlerTimeoutId = useRef(0); - const addCommentRef = useRef(null); const [initLoading, setInitLoading] = useState(true); const [selectedOutlineCommentId, setSelectedOutlineCommentId] = useState(''); const { isLoadingIds, patchComment } = useUpdateComment(); const currentUser = useCurrentUser(); - const [manageMarkdownEditIds, setManangeMarkdownEditIds] = useState([]); + const [manageMarkdownEditIds, setManageMarkdownEditIds] = useState([]); const [loadingAlertData, manualAlertsData] = useFetchAlertData( getManualAlertIdsWithNoRuleId(caseData.comments) @@ -155,9 +154,9 @@ export const UserActionTree = React.memo( const handleManageMarkdownEditId = useCallback( (id: string) => { if (!manageMarkdownEditIds.includes(id)) { - setManangeMarkdownEditIds([...manageMarkdownEditIds, id]); + setManageMarkdownEditIds([...manageMarkdownEditIds, id]); } else { - setManangeMarkdownEditIds(manageMarkdownEditIds.filter((myId) => id !== myId)); + setManageMarkdownEditIds(manageMarkdownEditIds.filter((myId) => id !== myId)); } }, [manageMarkdownEditIds] @@ -206,8 +205,8 @@ export const UserActionTree = React.memo( (quote: string) => { const addCarrots = quote.replace(new RegExp('\r?\n', 'g'), ' \n> '); - if (addCommentRef && addCommentRef.current) { - addCommentRef.current.addQuote(`> ${addCarrots} \n`); + if (commentRefs.current && commentRefs.current[NEW_ID]) { + commentRefs.current[NEW_ID].addQuote(`> ${addCarrots} \n`); } handleOutlineComment('add-comment'); @@ -223,27 +222,12 @@ export const UserActionTree = React.memo( [fetchUserActions, updateCase] ); - const MarkdownDescription = useMemo( - () => ( - { - onUpdateField({ key: DESCRIPTION_ID, value: content }); - }} - onChangeEditable={handleManageMarkdownEditId} - /> - ), - [caseData.description, handleManageMarkdownEditId, manageMarkdownEditIds, onUpdateField] - ); - const MarkdownNewComment = useMemo( () => ( (commentRefs.current[NEW_ID] = element)} caseId={caseId} userCanCrud={userCanCrud} - ref={addCommentRef} onCommentPosted={handleUpdate} onCommentSaving={handleManageMarkdownEditId.bind(null, NEW_ID)} showLoading={false} @@ -262,295 +246,244 @@ export const UserActionTree = React.memo( } }, [commentId, initLoading, isLoadingUserActions, isLoadingIds, handleOutlineComment]); - const descriptionCommentListObj: EuiCommentProps = useMemo( - () => ({ - username: ( - - ), - event: i18n.ADDED_DESCRIPTION, - 'data-test-subj': 'description-action', - timestamp: , - children: MarkdownDescription, - timelineIcon: ( - - ), - className: classNames({ - isEdit: manageMarkdownEditIds.includes(DESCRIPTION_ID), - }), - actions: ( - - ), - }), - [ - MarkdownDescription, - caseData, - getCaseDetailHrefWithCommentId, - handleManageMarkdownEditId, - handleManageQuote, - isLoadingDescription, - userCanCrud, - manageMarkdownEditIds, - ] - ); - const userActions: EuiCommentProps[] = useMemo( () => - caseUserActions.reduce( - (comments, action, index) => { - // Comment creation - if (action.commentId != null && action.action === 'create') { - const comment = caseData.comments.find((c) => c.id === action.commentId); - if ( - comment != null && - isRight(ContextTypeUserRt.decode(comment)) && - comment.type === CommentType.user - ) { - return [ - ...comments, - { - username: ( - - ), - 'data-test-subj': `comment-create-action-${comment.id}`, - timestamp: ( - - ), - className: classNames('userAction__comment', { - outlined: comment.id === selectedOutlineCommentId, - isEdit: manageMarkdownEditIds.includes(comment.id), - }), - children: ( - - ), - timelineIcon: ( - - ), - actions: ( - - ), - }, - ]; - } else if ( - comment != null && - isRight(AlertCommentRequestRt.decode(comment)) && - comment.type === CommentType.alert - ) { - // TODO: clean this up - const alertId = Array.isArray(comment.alertId) - ? comment.alertId.length > 0 - ? comment.alertId[0] - : '' - : comment.alertId; - - const alertIndex = Array.isArray(comment.index) - ? comment.index.length > 0 - ? comment.index[0] - : '' - : comment.index; - - if (isEmpty(alertId)) { - return comments; - } - - const ruleId = - comment?.rule?.id ?? manualAlertsData[alertId]?.signal?.rule?.id?.[0] ?? null; - const ruleName = - comment?.rule?.name ?? manualAlertsData[alertId]?.signal?.rule?.name?.[0] ?? null; - - return [ - ...comments, - ...(getRuleDetailsHref != null - ? [ - getAlertAttachment({ - action, - alertId, - getCaseDetailHrefWithCommentId, - getRuleDetailsHref, - index: alertIndex, - loadingAlertData, - onRuleDetailsClick, - ruleId, - ruleName, - onShowAlertDetails, - }), - ] - : []), - ]; - } else if (comment != null && comment.type === CommentType.generatedAlert) { - // TODO: clean this up - const alertIds = Array.isArray(comment.alertId) - ? comment.alertId - : [comment.alertId]; - - if (isEmpty(alertIds)) { - return comments; - } - - return [ - ...comments, - ...(getRuleDetailsHref != null - ? [ - getGeneratedAlertsAttachment({ - action, - alertIds, - getCaseDetailHrefWithCommentId, - getRuleDetailsHref, - onRuleDetailsClick, - renderInvestigateInTimelineActionComponent, - ruleId: comment.rule?.id ?? '', - ruleName: comment.rule?.name ?? i18n.UNKNOWN_RULE, - }), - ] - : []), - ]; - } - } - - // Connectors - if (action.actionField.length === 1 && action.actionField[0] === 'connector') { - const label = getConnectorLabelTitle({ action, connectors }); + caseUserActions.reduce((comments, action, index) => { + // Comment creation + if (action.commentId != null && action.action === 'create') { + const comment = caseData.comments.find((c) => c.id === action.commentId); + if ( + comment != null && + isRight(ContextTypeUserRt.decode(comment)) && + comment.type === CommentType.user + ) { return [ ...comments, - getUpdateAction({ - action, - label, - getCaseDetailHrefWithCommentId, - handleOutlineComment, - }), + { + username: ( + + ), + 'data-test-subj': `comment-create-action-${comment.id}`, + timestamp: ( + + ), + className: classNames('userAction__comment', { + outlined: comment.id === selectedOutlineCommentId, + isEdit: manageMarkdownEditIds.includes(comment.id), + }), + children: ( + (commentRefs.current[comment.id] = element)} + id={comment.id} + content={comment.comment} + isEditable={manageMarkdownEditIds.includes(comment.id)} + onChangeEditable={handleManageMarkdownEditId} + onSaveContent={handleSaveComment.bind(null, { + id: comment.id, + version: comment.version, + })} + /> + ), + timelineIcon: ( + + ), + actions: ( + + ), + }, ]; - } + } else if ( + comment != null && + isRight(AlertCommentRequestRt.decode(comment)) && + comment.type === CommentType.alert + ) { + // TODO: clean this up + const alertId = Array.isArray(comment.alertId) + ? comment.alertId.length > 0 + ? comment.alertId[0] + : '' + : comment.alertId; + + const alertIndex = Array.isArray(comment.index) + ? comment.index.length > 0 + ? comment.index[0] + : '' + : comment.index; + + if (isEmpty(alertId)) { + return comments; + } - // Pushed information - if (action.actionField.length === 1 && action.actionField[0] === 'pushed') { - const parsedValue = parseString(`${action.newValue}`); - const { firstPush, parsedConnectorId, parsedConnectorName } = getPushInfo( - caseServices, - parsedValue, - index - ); + const ruleId = + comment?.rule?.id ?? manualAlertsData[alertId]?.signal?.rule?.id?.[0] ?? null; + const ruleName = + comment?.rule?.name ?? manualAlertsData[alertId]?.signal?.rule?.name?.[0] ?? null; - const label = getPushedServiceLabelTitle(action, firstPush); - - const showTopFooter = - action.action === 'push-to-service' && - index === caseServices[parsedConnectorId]?.lastPushIndex; - - const showBottomFooter = - action.action === 'push-to-service' && - index === caseServices[parsedConnectorId]?.lastPushIndex && - caseServices[parsedConnectorId].hasDataToPush; - - let footers: EuiCommentProps[] = []; - - if (showTopFooter) { - footers = [ - ...footers, - { - username: '', - type: 'update', - event: i18n.ALREADY_PUSHED_TO_SERVICE(`${parsedConnectorName}`), - timelineIcon: 'sortUp', - 'data-test-subj': 'top-footer', - }, - ]; - } + return [ + ...comments, + ...(getRuleDetailsHref != null + ? [ + getAlertAttachment({ + action, + alertId, + getCaseDetailHrefWithCommentId, + getRuleDetailsHref, + index: alertIndex, + loadingAlertData, + onRuleDetailsClick, + ruleId, + ruleName, + onShowAlertDetails, + }), + ] + : []), + ]; + } else if (comment != null && comment.type === CommentType.generatedAlert) { + // TODO: clean this up + const alertIds = Array.isArray(comment.alertId) ? comment.alertId : [comment.alertId]; - if (showBottomFooter) { - footers = [ - ...footers, - { - username: '', - type: 'update', - event: i18n.REQUIRED_UPDATE_TO_SERVICE(`${parsedConnectorName}`), - timelineIcon: 'sortDown', - 'data-test-subj': 'bottom-footer', - }, - ]; + if (isEmpty(alertIds)) { + return comments; } return [ ...comments, - getUpdateAction({ - action, - label, - getCaseDetailHrefWithCommentId, - handleOutlineComment, - }), - ...footers, + ...(getRuleDetailsHref != null + ? [ + getGeneratedAlertsAttachment({ + action, + alertIds, + getCaseDetailHrefWithCommentId, + getRuleDetailsHref, + onRuleDetailsClick, + renderInvestigateInTimelineActionComponent, + ruleId: comment.rule?.id ?? '', + ruleName: comment.rule?.name ?? i18n.UNKNOWN_RULE, + }), + ] + : []), ]; } + } - // title, description, comment updates, tags - if ( - action.actionField.length === 1 && - ['title', 'description', 'comment', 'tags', 'status'].includes(action.actionField[0]) - ) { - const myField = action.actionField[0]; - const label: string | JSX.Element = getLabelTitle({ + // Connectors + if (action.actionField.length === 1 && action.actionField[0] === 'connector') { + const label = getConnectorLabelTitle({ action, connectors }); + return [ + ...comments, + getUpdateAction({ action, - field: myField, - }); + label, + getCaseDetailHrefWithCommentId, + handleOutlineComment, + }), + ]; + } - return [ - ...comments, - getUpdateAction({ - action, - label, - getCaseDetailHrefWithCommentId, - handleOutlineComment, - }), + // Pushed information + if (action.actionField.length === 1 && action.actionField[0] === 'pushed') { + const parsedValue = parseString(`${action.newValue}`); + const { firstPush, parsedConnectorId, parsedConnectorName } = getPushInfo( + caseServices, + parsedValue, + index + ); + + const label = getPushedServiceLabelTitle(action, firstPush); + + const showTopFooter = + action.action === 'push-to-service' && + index === caseServices[parsedConnectorId]?.lastPushIndex; + + const showBottomFooter = + action.action === 'push-to-service' && + index === caseServices[parsedConnectorId]?.lastPushIndex && + caseServices[parsedConnectorId].hasDataToPush; + + let footers: EuiCommentProps[] = []; + + if (showTopFooter) { + footers = [ + ...footers, + { + username: '', + type: 'update', + event: i18n.ALREADY_PUSHED_TO_SERVICE(`${parsedConnectorName}`), + timelineIcon: 'sortUp', + 'data-test-subj': 'top-footer', + }, ]; } - return comments; - }, - [descriptionCommentListObj] - ), + if (showBottomFooter) { + footers = [ + ...footers, + { + username: '', + type: 'update', + event: i18n.REQUIRED_UPDATE_TO_SERVICE(`${parsedConnectorName}`), + timelineIcon: 'sortDown', + 'data-test-subj': 'bottom-footer', + }, + ]; + } + + return [ + ...comments, + getUpdateAction({ + action, + label, + getCaseDetailHrefWithCommentId, + handleOutlineComment, + }), + ...footers, + ]; + } + + // title, description, comment updates, tags + if ( + action.actionField.length === 1 && + ['title', 'description', 'comment', 'tags', 'status'].includes(action.actionField[0]) + ) { + const myField = action.actionField[0]; + const label: string | JSX.Element = getLabelTitle({ + action, + field: myField, + }); + + return [ + ...comments, + getUpdateAction({ + action, + label, + getCaseDetailHrefWithCommentId, + handleOutlineComment, + }), + ]; + } + + return comments; + }, []), [ caseUserActions, - descriptionCommentListObj, caseData.comments, selectedOutlineCommentId, manageMarkdownEditIds, @@ -605,24 +538,47 @@ export const UserActionTree = React.memo( } catch (e) {} } - if (incomingEmbeddablePackage) { + console.error('incomingEmbeddablePackage', incomingEmbeddablePackage, draftComment); + + if (true || incomingEmbeddablePackage) { console.error('incomingEmbeddablePackage', incomingEmbeddablePackage); if (draftComment) { if (!draftComment.commentId) { - addCommentRef.current?.setComment(draftComment.comment); - const buttons = addCommentRef.current?.editorRef?.toolbar?.querySelector( - '[aria-label="Insert lens link"]' - ); - buttons.click(); + if (commentRefs.current && commentRefs.current[NEW_ID]) { + commentRefs.current[NEW_ID].setComment(draftComment.comment); + const buttons = commentRefs.current[NEW_ID].toolbar?.querySelector( + '[aria-label="Insert lens link"]' + ); + buttons.click(); + } } if (draftComment.commentId) { - setManangeMarkdownEditIds([draftComment.commentId]); + if (!manageMarkdownEditIds.includes(draftComment.commentId)) { + setManageMarkdownEditIds([draftComment.commentId]); + } + + if ( + commentRefs.current && + commentRefs.current[draftComment.commentId] && + commentRefs.current[draftComment.commentId].editor?.textarea && + commentRefs.current[draftComment.commentId].editor?.toolbar + ) { + commentRefs.current[draftComment.commentId].setComment(draftComment.comment); + const lensPluginButton = commentRefs.current[ + draftComment.commentId + ].editor?.toolbar?.querySelector('[aria-label="Insert lens link"]'); + if (lensPluginButton) { + lensPluginButton.click(); + } + } } } } - }, []); + }, [embeddable, manageMarkdownEditIds, storage]); + + console.error('markdownEditorRefs', commentRefs.current); return ( <> diff --git a/x-pack/plugins/cases/public/components/user_action_tree/user_action_markdown.tsx b/x-pack/plugins/cases/public/components/user_action_tree/user_action_markdown.tsx index 19cc804786af1..97a1d39d096ba 100644 --- a/x-pack/plugins/cases/public/components/user_action_tree/user_action_markdown.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/user_action_markdown.tsx @@ -6,7 +6,7 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty, EuiButton } from '@elastic/eui'; -import React, { useCallback } from 'react'; +import React, { forwardRef, useCallback, useImperativeHandle, useRef } from 'react'; import styled from 'styled-components'; import * as i18n from '../case_view/translations'; @@ -25,84 +25,99 @@ interface UserActionMarkdownProps { onChangeEditable: (id: string) => void; onSaveContent: (content: string) => void; } -export const UserActionMarkdown = ({ - id, - content, - isEditable, - onChangeEditable, - onSaveContent, -}: UserActionMarkdownProps) => { - const initialState = { content }; - const { form } = useForm({ - defaultValue: initialState, - options: { stripEmptyFields: false }, - schema, - }); - const fieldName = 'content'; - const { submit } = form; +interface UserActionMarkdownRefObject { + setComment: (newComment: string) => void; +} + +export const UserActionMarkdown = forwardRef( + ({ id, content, isEditable, onChangeEditable, onSaveContent }, ref) => { + const editorRef = useRef(); + const initialState = { content }; + const { form } = useForm({ + defaultValue: initialState, + options: { stripEmptyFields: false }, + schema, + }); + + const fieldName = 'content'; + const { setFieldValue, submit } = form; + + const handleCancelAction = useCallback(() => { + onChangeEditable(id); + }, [id, onChangeEditable]); + + const handleSaveAction = useCallback(async () => { + const { isValid, data } = await submit(); + if (isValid) { + onSaveContent(data.content); + } + onChangeEditable(id); + }, [id, onChangeEditable, onSaveContent, submit]); - const handleCancelAction = useCallback(() => { - onChangeEditable(id); - }, [id, onChangeEditable]); + const setComment = useCallback( + (newComment) => { + setFieldValue(fieldName, newComment); + }, + [setFieldValue] + ); - const handleSaveAction = useCallback(async () => { - const { isValid, data } = await submit(); - if (isValid) { - onSaveContent(data.content); - } - onChangeEditable(id); - }, [id, onChangeEditable, onSaveContent, submit]); + useImperativeHandle(ref, () => ({ + setComment, + editor: editorRef.current, + })); - const renderButtons = useCallback( - ({ cancelAction, saveAction }) => ( - - - - {i18n.CANCEL} - - - - - {i18n.SAVE} - - - - ), - [] - ); + const renderButtons = useCallback( + ({ cancelAction, saveAction }) => ( + + + + {i18n.CANCEL} + + + + + {i18n.SAVE} + + + + ), + [] + ); - return isEditable ? ( -
- - - ) : ( - - {content} - - ); -}; + return isEditable ? ( +
+ + + ) : ( + + {content} + + ); + } +); diff --git a/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx b/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx index cd9c7e269e973..a65c8e6732e44 100644 --- a/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx +++ b/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx @@ -283,7 +283,6 @@ export const runSaveLensVisualization = async ( onAppLeave?.((actions) => { return actions.default(); }); - console.error('redirectToOrigin', newInput, saveProps); redirectToOrigin({ input: newInput, isCopied: saveProps.newCopyOnSave }); return; } else if (saveProps.dashboardId) { diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/editor.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/editor.tsx index 81c1eace0b83f..12084a17e888a 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/editor.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/editor.tsx @@ -37,8 +37,6 @@ const MarkdownEditorComponent: React.FC = ({ [] ); - console.error('editorId', editorId); - return ( (null); +export const CommentEditorContext = React.createContext(null); diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/helpers.ts b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/helpers.ts new file mode 100644 index 0000000000000..45fd51ad443a1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/helpers.ts @@ -0,0 +1,90 @@ +/* + * 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. + */ + +import type { IndexPattern } from 'src/plugins/data/public'; +import type { + TypedLensByValueInput, + PersistedIndexPatternLayer, + XYState, +} from '../../../../../../../lens/public'; + +// Generate a Lens state based on some app-specific input parameters. +// `TypedLensByValueInput` can be used for type-safety - it uses the same interfaces as Lens-internal code. +export const getLensAttributes = ( + defaultIndexPattern: IndexPattern +): TypedLensByValueInput['attributes'] => { + const dataLayer: PersistedIndexPatternLayer = { + columnOrder: ['col1', 'col2'], + columns: { + col2: { + dataType: 'number', + isBucketed: false, + label: 'Count of records', + operationType: 'count', + scale: 'ratio', + sourceField: 'Records', + }, + col1: { + dataType: 'date', + isBucketed: true, + label: '@timestamp', + operationType: 'date_histogram', + params: { interval: 'auto' }, + scale: 'interval', + sourceField: defaultIndexPattern.timeFieldName!, + }, + }, + }; + + const xyConfig: XYState = { + axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true }, + fittingFunction: 'None', + gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true }, + layers: [ + { + accessors: ['col2'], + layerId: 'layer1', + seriesType: 'bar_stacked', + xAccessor: 'col1', + yConfig: [{ forAccessor: 'col2', color: undefined }], + }, + ], + legend: { isVisible: true, position: 'right' }, + preferredSeriesType: 'bar_stacked', + tickLabelsVisibilitySettings: { x: true, yLeft: true, yRight: true }, + valueLabels: 'hide', + }; + + return { + visualizationType: 'lnsXY', + title: '', + references: [ + { + id: defaultIndexPattern.id!, + name: 'indexpattern-datasource-current-indexpattern', + type: 'index-pattern', + }, + { + id: defaultIndexPattern.id!, + name: 'indexpattern-datasource-layer-layer1', + type: 'index-pattern', + }, + ], + state: { + datasourceStates: { + indexpattern: { + layers: { + layer1: dataLayer, + }, + }, + }, + filters: [], + query: { language: 'kuery', query: '' }, + visualization: xyConfig, + }, + }; +}; diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/index.ts b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/index.ts index 493d960606414..b0320954c97f0 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/index.ts +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/index.ts @@ -8,6 +8,11 @@ import { plugin } from './plugin'; import { LensParser } from './parser'; import { LensMarkDownRenderer } from './processor'; -import { LensContext } from './context'; +import { CommentEditorContext } from './context'; -export { LensContext as context, plugin, LensParser as parser, LensMarkDownRenderer as renderer }; +export { + CommentEditorContext as context, + plugin, + LensParser as parser, + LensMarkDownRenderer as renderer, +}; diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx new file mode 100644 index 0000000000000..f077ca4d46d31 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx @@ -0,0 +1,66 @@ +/* + * 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. + */ + +import { EuiModal, EuiModalBody, EuiModalHeader, EuiModalHeaderTitle } from '@elastic/eui'; +import React from 'react'; + +import { SavedObjectFinderUi } from '../../../../../../../../../src/plugins/saved_objects/public'; +import { useKibana } from '../../../../lib/kibana'; +import { ModalContainer } from './modal_container'; + +const LensSavedObjectsModalComponent = ({ onClose, onChoose }) => { + const { savedObjects, uiSettings } = useKibana().services; + + return ( + + + + +

{'Modal title'}

+
+
+ + + 'lensApp', + name: 'Lens', + includeFields: ['*'], + // i18n.translate( + // 'xpack.transform.newTransform.searchSelection.savedObjectType.search', + // { + // defaultMessage: 'Lens', + // } + // ), + }, + ]} + fixedPageSize={10} + uiSettings={uiSettings} + savedObjects={savedObjects} + /> + +
+
+ ); +}; + +export const LensSavedObjectsModal = React.memo(LensSavedObjectsModalComponent); diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/modal_container.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/modal_container.tsx new file mode 100644 index 0000000000000..0f70e80deed41 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/modal_container.tsx @@ -0,0 +1,16 @@ +/* + * 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. + */ + +import styled from 'styled-components'; + +export const ModalContainer = styled.div` + width: ${({ theme }) => theme.eui.euiBreakpoints.m}; + + .euiModalBody { + min-height: 300px; + } +`; diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx index f5eda1a4fd6e8..921ed627fafa1 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx @@ -9,7 +9,6 @@ import dateMath from '@elastic/datemath'; import { EuiFieldText, - EuiModal, EuiModalBody, EuiModalHeader, EuiModalHeaderTitle, @@ -24,107 +23,20 @@ import { EuiFlexGroup, EuiFormRow, } from '@elastic/eui'; +// @ts-expect-error update types import { insertText } from '@elastic/eui/lib/components/markdown_editor/markdown_actions'; -import { IndexPattern } from 'src/plugins/data/public'; import React, { useCallback, useContext, useMemo, useRef, useEffect, useState } from 'react'; -import styled from 'styled-components'; import moment, { Moment } from 'moment'; import { useLocation } from 'react-router-dom'; -import { SavedObjectFinderUi } from '../../../../../../../../../src/plugins/saved_objects/public'; -import { - TypedLensByValueInput, - PersistedIndexPatternLayer, - XYState, -} from '../../../../../../../lens/public'; import { useKibana } from '../../../../lib/kibana'; import { LensMarkDownRenderer } from './processor'; import { ID } from './constants'; import * as i18n from './translations'; -import { LensContext } from './context'; - -const ModalContainer = styled.div` - width: ${({ theme }) => theme.eui.euiBreakpoints.m}; - - .euiModalBody { - min-height: 300px; - } -`; - -// Generate a Lens state based on some app-specific input parameters. -// `TypedLensByValueInput` can be used for type-safety - it uses the same interfaces as Lens-internal code. -function getLensAttributes(defaultIndexPattern: IndexPattern): TypedLensByValueInput['attributes'] { - const dataLayer: PersistedIndexPatternLayer = { - columnOrder: ['col1', 'col2'], - columns: { - col2: { - dataType: 'number', - isBucketed: false, - label: 'Count of records', - operationType: 'count', - scale: 'ratio', - sourceField: 'Records', - }, - col1: { - dataType: 'date', - isBucketed: true, - label: '@timestamp', - operationType: 'date_histogram', - params: { interval: 'auto' }, - scale: 'interval', - sourceField: defaultIndexPattern.timeFieldName!, - }, - }, - }; - - const xyConfig: XYState = { - axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true }, - fittingFunction: 'None', - gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true }, - layers: [ - { - accessors: ['col2'], - layerId: 'layer1', - seriesType: 'bar_stacked', - xAccessor: 'col1', - yConfig: [{ forAccessor: 'col2', color: undefined }], - }, - ], - legend: { isVisible: true, position: 'right' }, - preferredSeriesType: 'bar_stacked', - tickLabelsVisibilitySettings: { x: true, yLeft: true, yRight: true }, - valueLabels: 'hide', - }; - - return { - visualizationType: 'lnsXY', - title: '', - references: [ - { - id: defaultIndexPattern.id!, - name: 'indexpattern-datasource-current-indexpattern', - type: 'index-pattern', - }, - { - id: defaultIndexPattern.id!, - name: 'indexpattern-datasource-layer-layer1', - type: 'index-pattern', - }, - ], - state: { - datasourceStates: { - indexpattern: { - layers: { - layer1: dataLayer, - }, - }, - }, - filters: [], - query: { language: 'kuery', query: '' }, - visualization: xyConfig, - }, - }; -} +import { CommentEditorContext } from './context'; +import { LensSavedObjectsModal } from './lens_saved_objects_modal'; +import { ModalContainer } from './modal_container'; +import { getLensAttributes } from './helpers'; interface LensEditorProps { editMode?: boolean | null; @@ -164,10 +76,10 @@ const LensEditorComponent: React.FC = ({ node, onClosePopover, ); const [lensTitle, setLensTitle] = useState(node?.title ?? ''); const [showLensSavedObjectsModal, setShowLensSavedObjectsModal] = useState(false); - const context = useContext(LensContext); + const commentEditorContext = useContext(CommentEditorContext); const markdownContext = useContext(EuiMarkdownContext); - console.error('contextcontextcontext', context); + console.error('contextcontextcontext', commentEditorContext); console.error('rest', timefilter.getTime(), rest); @@ -183,7 +95,24 @@ const LensEditorComponent: React.FC = ({ node, onClosePopover, }, []); const handleAdd = useCallback(() => { + let draftComment; + if (storage.get('xpack.cases.commentDraft')) { + try { + draftComment = JSON.parse(storage.get('xpack.cases.commentDraft')); + } catch (e) {} + } + if (node) { + markdownContext.replaceNode( + draftComment.position, + `!{lens${JSON.stringify({ + startDate, + endDate, + title: lensTitle, + attributes: lensEmbeddableAttributes, + })}}` + ); + onClosePopover(); return; } @@ -200,24 +129,29 @@ const LensEditorComponent: React.FC = ({ node, onClosePopover, } ); } - }, [lensEmbeddableAttributes, onSave, startDate, endDate, lensTitle]); - - const originatingPath = useMemo(() => { - const commentId = context?.editorId; - - if (!commentId) { - return `${location.pathname}${location.search}`; - } + }, [ + storage, + node, + lensEmbeddableAttributes, + markdownContext, + onClosePopover, + onSave, + startDate, + endDate, + lensTitle, + ]); - return `${location.pathname}/${commentId}${location.search}`; - }, [context?.editorId, location.pathname, location.search]); + const originatingPath = useMemo(() => `${location.pathname}${location.search}`, [ + location.pathname, + location.search, + ]); const handleEditInLensClick = useCallback(async () => { storage.set( 'xpack.cases.commentDraft', JSON.stringify({ - commentId: context?.editorId, - comment: context?.value, + commentId: commentEditorContext?.editorId, + comment: commentEditorContext?.value, position: node?.position, title: lensTitle, }) @@ -240,8 +174,8 @@ const LensEditorComponent: React.FC = ({ node, onClosePopover, ); }, [ storage, - context?.editorId, - context?.value, + commentEditorContext?.editorId, + commentEditorContext?.value, node?.position, lensTitle, lens, @@ -264,13 +198,24 @@ const LensEditorComponent: React.FC = ({ node, onClosePopover, setLensEmbeddableAttributes(incomingEmbeddablePackage?.input.attributes); const lensTime = timefilter.getTime(); if (lensTime?.from && lensTime?.to) { - setStartDate(dateMath.parse(lensTime.from!)); - setEndDate(dateMath.parse(lensTime.to!)); + setStartDate(dateMath.parse(lensTime.from)!); + setEndDate(dateMath.parse(lensTime.to)!); } } + let draftComment; + if (storage.get('xpack.cases.commentDraft')) { + try { + draftComment = JSON.parse(storage.get('xpack.cases.commentDraft')); + } catch (e) {} + } + + if (draftComment.title) { + setLensTitle(draftComment.title); + } + console.error('stoargesgeet', storage.get('xpack.cases.commentDraft')); - }, [embeddable, storage]); + }, [embeddable, storage, timefilter]); console.error('insertText', insertText); @@ -350,57 +295,17 @@ const LensEditorComponent: React.FC = ({ node, onClosePopover,
{showLensSavedObjectsModal ? ( - setShowLensSavedObjectsModal(false)}> - - - -

{'Modal title'}

-
-
- - - { - console.error('apya', savedObjectId, savedObjectType, fullName, savedObject); - setLensEmbeddableAttributes({ - ...savedObject.attributes, - references: savedObject.references, - }); - setShowLensSavedObjectsModal(false); - }} - showFilter - noItemsMessage={ - 'No matching lens found.' - - // i18n.translate( - // 'xpack.transform.newTransform.searchSelection.notFoundLabel', - // { - // defaultMessage: 'No matching lens found.', - // } - // ) - } - savedObjectMetaData={[ - { - type: 'lens', - getIconForSavedObject: () => 'lensApp', - name: 'Lens', - includeFields: ['*'], - // i18n.translate( - // 'xpack.transform.newTransform.searchSelection.savedObjectType.search', - // { - // defaultMessage: 'Lens', - // } - // ), - }, - ]} - fixedPageSize={10} - uiSettings={uiSettings} - savedObjects={savedObjects} - /> - -
-
+ setShowLensSavedObjectsModal(false)} + onChoose={(savedObjectId, savedObjectType, fullName, savedObject) => { + console.error('apya', savedObjectId, savedObjectType, fullName, savedObject); + setLensEmbeddableAttributes({ + ...savedObject.attributes, + references: savedObject.references, + }); + setShowLensSavedObjectsModal(false); + }} + /> ) : null} ); @@ -416,12 +321,10 @@ export const plugin: EuiMarkdownEditorUiPlugin = { }, helpText: ( - {'[title](url)'} + {'{lens!{}}'} ), editor: function editor({ node, onSave, onCancel, ...rest }) { - console.error('editorr', node); - console.error('ssssssa', rest); return ; }, }; From d3ff6b20aecc3f71af592f7fca6d12eda884fcb4 Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Sun, 27 Jun 2021 17:50:06 +0200 Subject: [PATCH 12/41] cleanup --- ...embeddable-public.embeddableeditorstate.md | 1 + ...c.embeddableeditorstate.originatingpath.md | 11 + .../public/lib/state_transfer/types.ts | 2 +- src/plugins/embeddable/public/public.api.md | 2 + .../saved_objects/public/finder/index.ts | 1 + src/plugins/saved_objects/public/index.ts | 7 +- .../embedded_lens_example/public/app.tsx | 4 +- x-pack/plugins/cases/README.md | 10 + .../cases/public/components/create/index.tsx | 73 +-- .../public/components/lens_context/index.tsx | 18 +- .../lens_context/use_lens_context.ts | 4 +- .../components/markdown_editor/editor.tsx | 43 +- .../components/markdown_editor/eui_form.tsx | 68 +-- .../components/user_action_tree/index.tsx | 519 ++++++++++-------- x-pack/plugins/cases/public/types.ts | 8 +- x-pack/plugins/cases/tsconfig.json | 1 + x-pack/plugins/lens/public/plugin.ts | 20 +- .../shared/exploratory_view/header/header.tsx | 4 +- x-pack/plugins/security_solution/kibana.json | 2 +- .../public/cases/components/create/index.tsx | 9 + .../components/markdown_editor/editor.tsx | 100 ++-- .../components/markdown_editor/eui_form.tsx | 77 +-- .../markdown_editor/plugins/lens/context.tsx | 5 +- .../markdown_editor/plugins/lens/helpers.ts | 8 +- .../plugins/lens/lens_saved_objects_modal.tsx | 23 +- .../markdown_editor/plugins/lens/plugin.tsx | 144 ++--- .../plugins/lens/processor.tsx | 64 ++- .../plugins/security_solution/public/types.ts | 2 +- 28 files changed, 717 insertions(+), 513 deletions(-) create mode 100644 docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddableeditorstate.originatingpath.md diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddableeditorstate.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddableeditorstate.md index 63302f50204fe..d6965fe0eb1b3 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddableeditorstate.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddableeditorstate.md @@ -18,5 +18,6 @@ export interface EmbeddableEditorState | --- | --- | --- | | [embeddableId](./kibana-plugin-plugins-embeddable-public.embeddableeditorstate.embeddableid.md) | string | | | [originatingApp](./kibana-plugin-plugins-embeddable-public.embeddableeditorstate.originatingapp.md) | string | | +| [originatingPath](./kibana-plugin-plugins-embeddable-public.embeddableeditorstate.originatingpath.md) | string | | | [valueInput](./kibana-plugin-plugins-embeddable-public.embeddableeditorstate.valueinput.md) | EmbeddableInput | | diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddableeditorstate.originatingpath.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddableeditorstate.originatingpath.md new file mode 100644 index 0000000000000..e255f11f8a059 --- /dev/null +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddableeditorstate.originatingpath.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [EmbeddableEditorState](./kibana-plugin-plugins-embeddable-public.embeddableeditorstate.md) > [originatingPath](./kibana-plugin-plugins-embeddable-public.embeddableeditorstate.originatingpath.md) + +## EmbeddableEditorState.originatingPath property + +Signature: + +```typescript +originatingPath?: string; +``` diff --git a/src/plugins/embeddable/public/lib/state_transfer/types.ts b/src/plugins/embeddable/public/lib/state_transfer/types.ts index 0180aa8be2c92..10686aa3e597f 100644 --- a/src/plugins/embeddable/public/lib/state_transfer/types.ts +++ b/src/plugins/embeddable/public/lib/state_transfer/types.ts @@ -17,7 +17,7 @@ export const EMBEDDABLE_EDITOR_STATE_KEY = 'embeddable_editor_state'; */ export interface EmbeddableEditorState { originatingApp: string; - originatingPath: string | undefined; + originatingPath?: string; embeddableId?: string; valueInput?: EmbeddableInput; } diff --git a/src/plugins/embeddable/public/public.api.md b/src/plugins/embeddable/public/public.api.md index 98c48dbd848b0..436616de3304d 100644 --- a/src/plugins/embeddable/public/public.api.md +++ b/src/plugins/embeddable/public/public.api.md @@ -366,6 +366,8 @@ export interface EmbeddableEditorState { // (undocumented) originatingApp: string; // (undocumented) + originatingPath?: string; + // (undocumented) valueInput?: EmbeddableInput; } diff --git a/src/plugins/saved_objects/public/finder/index.ts b/src/plugins/saved_objects/public/finder/index.ts index edec012d90d6f..de6a54795fce5 100644 --- a/src/plugins/saved_objects/public/finder/index.ts +++ b/src/plugins/saved_objects/public/finder/index.ts @@ -9,5 +9,6 @@ export { SavedObjectMetaData, SavedObjectFinderUi, + SavedObjectFinderUiProps, getSavedObjectFinder, } from './saved_object_finder'; diff --git a/src/plugins/saved_objects/public/index.ts b/src/plugins/saved_objects/public/index.ts index 84c39168d82c2..bc84298a63717 100644 --- a/src/plugins/saved_objects/public/index.ts +++ b/src/plugins/saved_objects/public/index.ts @@ -17,7 +17,12 @@ export { SaveResult, showSaveModal, } from './save_modal'; -export { getSavedObjectFinder, SavedObjectFinderUi, SavedObjectMetaData } from './finder'; +export { + getSavedObjectFinder, + SavedObjectFinderUi, + SavedObjectFinderUiProps, + SavedObjectMetaData, +} from './finder'; export { SavedObjectLoader, SavedObjectLoaderFindOptions, diff --git a/x-pack/examples/embedded_lens_example/public/app.tsx b/x-pack/examples/embedded_lens_example/public/app.tsx index a13ddbbd79ef0..a755421c23ef1 100644 --- a/x-pack/examples/embedded_lens_example/public/app.tsx +++ b/x-pack/examples/embedded_lens_example/public/app.tsx @@ -170,7 +170,9 @@ export const App = (props: { timeRange: time, attributes: getLensAttributes(props.defaultIndexPattern!, color), }, - true + { + openInNewTab: true, + } ); // eslint-disable-next-line no-bitwise const newColor = '#' + ((Math.random() * 0xffffff) << 0).toString(16); diff --git a/x-pack/plugins/cases/README.md b/x-pack/plugins/cases/README.md index cfff8c79ee2d4..9c6bbaf5088e3 100644 --- a/x-pack/plugins/cases/README.md +++ b/x-pack/plugins/cases/README.md @@ -43,6 +43,16 @@ cases: CasesUiStart; cases.getCreateCase({ onCancel: handleSetIsCancel, onSuccess, + lensIntegration?: { + plugins: { + parsingPlugin, + processingPluginRenderer, + uiPlugin, + }, + hooks: { + useInsertTimeline, + }, + } timelineIntegration?: { plugins: { parsingPlugin, diff --git a/x-pack/plugins/cases/public/components/create/index.tsx b/x-pack/plugins/cases/public/components/create/index.tsx index 7f8b8f664529e..5f6c832a22a30 100644 --- a/x-pack/plugins/cases/public/components/create/index.tsx +++ b/x-pack/plugins/cases/public/components/create/index.tsx @@ -17,6 +17,7 @@ import { SubmitCaseButton } from './submit_button'; import { Case } from '../../containers/types'; import { CaseType } from '../../../common'; import { CasesTimelineIntegration, CasesTimelineIntegrationProvider } from '../timeline_context'; +import { CasesLensIntegration, CasesLensIntegrationProvider } from '../lens_context'; import { fieldName as descriptionFieldName } from './description'; import { InsertTimeline } from '../insert_timeline'; import { UsePostComment } from '../../containers/use_post_comment'; @@ -38,6 +39,7 @@ export interface CreateCaseProps extends Owner { hideConnectorServiceNowSir?: boolean; onCancel: () => void; onSuccess: (theCase: Case) => Promise; + lensIntegration?: CasesLensIntegration; timelineIntegration?: CasesTimelineIntegration; withSteps?: boolean; } @@ -49,45 +51,48 @@ const CreateCaseComponent = ({ disableAlerts, onCancel, onSuccess, + lensIntegration, timelineIntegration, withSteps, }: Omit) => ( - - + - - - - - {i18n.CANCEL} - - - - - - - - - + onSuccess={onSuccess} + > + + + + + + {i18n.CANCEL} + + + + + + + + + + ); diff --git a/x-pack/plugins/cases/public/components/lens_context/index.tsx b/x-pack/plugins/cases/public/components/lens_context/index.tsx index 029d77208ec5a..208e2446a8ead 100644 --- a/x-pack/plugins/cases/public/components/lens_context/index.tsx +++ b/x-pack/plugins/cases/public/components/lens_context/index.tsx @@ -6,24 +6,24 @@ */ import React, { useState } from 'react'; -import { EuiMarkdownEditorUiPlugin, EuiMarkdownAstNodePosition } from '@elastic/eui'; +import { EuiMarkdownEditorUiPlugin } from '@elastic/eui'; import { Plugin } from 'unified'; +import { TypedLensByValueInput } from '../../../../lens/public'; interface LensProcessingPluginRendererProps { - id: string | null; - title: string; - graphEventId?: string; - type: 'lens'; - [key: string]: string | null | undefined; + attributes: TypedLensByValueInput['attributes'] | null; + id?: string | null; + title?: string | null; + startDate?: string | null; + endDate?: string | null; + viewMode?: boolean | undefined; } export interface CasesLensIntegration { editor_context: any; editor_plugins: { parsingPlugin: Plugin; - processingPluginRenderer: React.FC< - LensProcessingPluginRendererProps & { position: EuiMarkdownAstNodePosition } - >; + processingPluginRenderer: React.FC; uiPlugin: EuiMarkdownEditorUiPlugin; }; } diff --git a/x-pack/plugins/cases/public/components/lens_context/use_lens_context.ts b/x-pack/plugins/cases/public/components/lens_context/use_lens_context.ts index 4ff8128b16ee3..c0d5d7eb29121 100644 --- a/x-pack/plugins/cases/public/components/lens_context/use_lens_context.ts +++ b/x-pack/plugins/cases/public/components/lens_context/use_lens_context.ts @@ -8,6 +8,4 @@ import { useContext } from 'react'; import { CasesLensIntegrationContext } from '.'; -export const useCasesLensIntegrationContext = () => { - return useContext(CasesLensIntegrationContext); -}; +export const useCasesLensIntegrationContext = () => useContext(CasesLensIntegrationContext); diff --git a/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx b/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx index a8cf7aea0e884..a7862baaf4d28 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx @@ -5,9 +5,18 @@ * 2.0. */ -import React, { memo, forwardRef, useCallback, useRef, useState, useImperativeHandle } from 'react'; +import React, { + memo, + forwardRef, + useCallback, + useRef, + useState, + useImperativeHandle, + ElementRef, +} from 'react'; import { PluggableList } from 'unified'; import { EuiMarkdownEditor, EuiMarkdownEditorUiPlugin } from '@elastic/eui'; +import { ContextShape } from '@elastic/eui/src/components/markdown_editor/markdown_context'; import { usePlugins } from './use_plugins'; import { useCasesLensIntegrationContext } from '../lens_context/use_lens_context'; @@ -23,26 +32,38 @@ interface MarkdownEditorProps { value: string; } -const MarkdownEditorComponent = forwardRef( - ({ ariaLabel, dataTestSubj, editorId, height, onChange, value }: MarkdownEditorProps, ref) => { +type EuiMarkdownEditorRef = ElementRef; + +export interface MarkdownEditorRef { + textarea: HTMLTextAreaElement | null; + replaceNode: ContextShape['replaceNode']; + toolbar: HTMLDivElement | null; +} + +const MarkdownEditorComponent = forwardRef( + ({ ariaLabel, dataTestSubj, editorId, height, onChange, value }, ref) => { const [markdownErrorMessages, setMarkdownErrorMessages] = useState([]); const onParse = useCallback((err, { messages }) => { setMarkdownErrorMessages(err ? [err] : messages); }, []); const { parsingPlugins, processingPlugins, uiPlugins } = usePlugins(); const CasesLensIntegrationContextProvider = useCasesLensIntegrationContext()?.editor_context - .Provider; - const editorRef = useRef(null); - - console.error('useRef', ref?.current); + ?.Provider; + const editorRef = useRef(null); + // @ts-expect-error update types useImperativeHandle(ref, () => { - console.error('reft', ref, editorRef); + console.error('reft2222', ref, editorRef); + + if (!editorRef.current) { + return null; + } + + const editorNode = editorRef.current?.textarea?.closest('.euiMarkdownEditor'); + return { ...editorRef.current, - toolbar: editorRef.current.textarea - .closest('.euiMarkdownEditor') - .querySelector('.euiMarkdownEditorToolbar'), + toolbar: editorNode?.querySelector('.euiMarkdownEditorToolbar'), }; }); diff --git a/x-pack/plugins/cases/public/components/markdown_editor/eui_form.tsx b/x-pack/plugins/cases/public/components/markdown_editor/eui_form.tsx index 630fa8b8e1e66..34815efc032c4 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/eui_form.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/eui_form.tsx @@ -9,7 +9,7 @@ import React, { forwardRef } from 'react'; import styled from 'styled-components'; import { EuiMarkdownEditorProps, EuiFormRow, EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; import { FieldHook, getFieldValidityAndErrorMessage } from '../../common/shared_imports'; -import { MarkdownEditor } from './editor'; +import { MarkdownEditor, MarkdownEditorRef } from './editor'; type MarkdownEditorFormProps = EuiMarkdownEditorProps & { id: string; @@ -26,37 +26,39 @@ const BottomContentWrapper = styled(EuiFlexGroup)` `} `; -export const MarkdownEditorForm: React.FC = forwardRef( - ({ id, field, dataTestSubj, idAria, bottomRightContent }, ref) => { - const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); +export const MarkdownEditorForm = React.memo( + forwardRef( + ({ id, field, dataTestSubj, idAria, bottomRightContent }, ref) => { + const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); - return ( - - <> - - {bottomRightContent && ( - - {bottomRightContent} - - )} - - - ); - } + return ( + + <> + + {bottomRightContent && ( + + {bottomRightContent} + + )} + + + ); + } + ) ); diff --git a/x-pack/plugins/cases/public/components/user_action_tree/index.tsx b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx index 253304da6de90..b142471c90897 100644 --- a/x-pack/plugins/cases/public/components/user_action_tree/index.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx @@ -23,7 +23,7 @@ import * as i18n from './translations'; import { useUpdateComment } from '../../containers/use_update_comment'; import { useCurrentUser, useKibana } from '../../common/lib/kibana'; -import { AddComment, AddCommentRefObject } from '../add_comment'; +import { AddComment } from '../add_comment'; import { ActionConnector, AlertCommentRequestRt, @@ -112,6 +112,7 @@ const MyEuiCommentList = styled(EuiCommentList)` `} `; +const DESCRIPTION_ID = 'description'; const NEW_ID = 'newComment'; export const UserActionTree = React.memo( @@ -205,7 +206,7 @@ export const UserActionTree = React.memo( (quote: string) => { const addCarrots = quote.replace(new RegExp('\r?\n', 'g'), ' \n> '); - if (commentRefs.current && commentRefs.current[NEW_ID]) { + if (commentRefs.current[NEW_ID]) { commentRefs.current[NEW_ID].addQuote(`> ${addCarrots} \n`); } @@ -222,12 +223,27 @@ export const UserActionTree = React.memo( [fetchUserActions, updateCase] ); + const MarkdownDescription = useMemo( + () => ( + { + onUpdateField({ key: DESCRIPTION_ID, value: content }); + }} + onChangeEditable={handleManageMarkdownEditId} + /> + ), + [caseData.description, handleManageMarkdownEditId, manageMarkdownEditIds, onUpdateField] + ); + const MarkdownNewComment = useMemo( () => ( (commentRefs.current[NEW_ID] = element)} caseId={caseId} userCanCrud={userCanCrud} + ref={(element) => (commentRefs.current[NEW_ID] = element)} onCommentPosted={handleUpdate} onCommentSaving={handleManageMarkdownEditId.bind(null, NEW_ID)} showLoading={false} @@ -246,253 +262,305 @@ export const UserActionTree = React.memo( } }, [commentId, initLoading, isLoadingUserActions, isLoadingIds, handleOutlineComment]); + const descriptionCommentListObj: EuiCommentProps = useMemo( + () => ({ + username: ( + + ), + event: i18n.ADDED_DESCRIPTION, + 'data-test-subj': 'description-action', + timestamp: , + children: MarkdownDescription, + timelineIcon: ( + + ), + className: classNames({ + isEdit: manageMarkdownEditIds.includes(DESCRIPTION_ID), + }), + actions: ( + + ), + }), + [ + MarkdownDescription, + caseData, + getCaseDetailHrefWithCommentId, + handleManageMarkdownEditId, + handleManageQuote, + isLoadingDescription, + userCanCrud, + manageMarkdownEditIds, + ] + ); + const userActions: EuiCommentProps[] = useMemo( () => - caseUserActions.reduce((comments, action, index) => { - // Comment creation - if (action.commentId != null && action.action === 'create') { - const comment = caseData.comments.find((c) => c.id === action.commentId); - if ( - comment != null && - isRight(ContextTypeUserRt.decode(comment)) && - comment.type === CommentType.user - ) { - return [ - ...comments, - { - username: ( - - ), - 'data-test-subj': `comment-create-action-${comment.id}`, - timestamp: ( - - ), - className: classNames('userAction__comment', { - outlined: comment.id === selectedOutlineCommentId, - isEdit: manageMarkdownEditIds.includes(comment.id), - }), - children: ( - (commentRefs.current[comment.id] = element)} - id={comment.id} - content={comment.comment} - isEditable={manageMarkdownEditIds.includes(comment.id)} - onChangeEditable={handleManageMarkdownEditId} - onSaveContent={handleSaveComment.bind(null, { - id: comment.id, - version: comment.version, - })} - /> - ), - timelineIcon: ( - - ), - actions: ( - - ), - }, - ]; - } else if ( - comment != null && - isRight(AlertCommentRequestRt.decode(comment)) && - comment.type === CommentType.alert - ) { - // TODO: clean this up - const alertId = Array.isArray(comment.alertId) - ? comment.alertId.length > 0 - ? comment.alertId[0] - : '' - : comment.alertId; - - const alertIndex = Array.isArray(comment.index) - ? comment.index.length > 0 - ? comment.index[0] - : '' - : comment.index; - - if (isEmpty(alertId)) { - return comments; + caseUserActions.reduce( + (comments, action, index) => { + // Comment creation + if (action.commentId != null && action.action === 'create') { + const comment = caseData.comments.find((c) => c.id === action.commentId); + if ( + comment != null && + isRight(ContextTypeUserRt.decode(comment)) && + comment.type === CommentType.user + ) { + return [ + ...comments, + { + username: ( + + ), + 'data-test-subj': `comment-create-action-${comment.id}`, + timestamp: ( + + ), + className: classNames('userAction__comment', { + outlined: comment.id === selectedOutlineCommentId, + isEdit: manageMarkdownEditIds.includes(comment.id), + }), + children: ( + (commentRefs.current[comment.id] = element)} + id={comment.id} + content={comment.comment} + isEditable={manageMarkdownEditIds.includes(comment.id)} + onChangeEditable={handleManageMarkdownEditId} + onSaveContent={handleSaveComment.bind(null, { + id: comment.id, + version: comment.version, + })} + /> + ), + timelineIcon: ( + + ), + actions: ( + + ), + }, + ]; + } else if ( + comment != null && + isRight(AlertCommentRequestRt.decode(comment)) && + comment.type === CommentType.alert + ) { + // TODO: clean this up + const alertId = Array.isArray(comment.alertId) + ? comment.alertId.length > 0 + ? comment.alertId[0] + : '' + : comment.alertId; + + const alertIndex = Array.isArray(comment.index) + ? comment.index.length > 0 + ? comment.index[0] + : '' + : comment.index; + + if (isEmpty(alertId)) { + return comments; + } + + const ruleId = + comment?.rule?.id ?? manualAlertsData[alertId]?.signal?.rule?.id?.[0] ?? null; + const ruleName = + comment?.rule?.name ?? manualAlertsData[alertId]?.signal?.rule?.name?.[0] ?? null; + + return [ + ...comments, + ...(getRuleDetailsHref != null + ? [ + getAlertAttachment({ + action, + alertId, + getCaseDetailHrefWithCommentId, + getRuleDetailsHref, + index: alertIndex, + loadingAlertData, + onRuleDetailsClick, + ruleId, + ruleName, + onShowAlertDetails, + }), + ] + : []), + ]; + } else if (comment != null && comment.type === CommentType.generatedAlert) { + // TODO: clean this up + const alertIds = Array.isArray(comment.alertId) + ? comment.alertId + : [comment.alertId]; + + if (isEmpty(alertIds)) { + return comments; + } + + return [ + ...comments, + ...(getRuleDetailsHref != null + ? [ + getGeneratedAlertsAttachment({ + action, + alertIds, + getCaseDetailHrefWithCommentId, + getRuleDetailsHref, + onRuleDetailsClick, + renderInvestigateInTimelineActionComponent, + ruleId: comment.rule?.id ?? '', + ruleName: comment.rule?.name ?? i18n.UNKNOWN_RULE, + }), + ] + : []), + ]; } + } - const ruleId = - comment?.rule?.id ?? manualAlertsData[alertId]?.signal?.rule?.id?.[0] ?? null; - const ruleName = - comment?.rule?.name ?? manualAlertsData[alertId]?.signal?.rule?.name?.[0] ?? null; - + // Connectors + if (action.actionField.length === 1 && action.actionField[0] === 'connector') { + const label = getConnectorLabelTitle({ action, connectors }); return [ ...comments, - ...(getRuleDetailsHref != null - ? [ - getAlertAttachment({ - action, - alertId, - getCaseDetailHrefWithCommentId, - getRuleDetailsHref, - index: alertIndex, - loadingAlertData, - onRuleDetailsClick, - ruleId, - ruleName, - onShowAlertDetails, - }), - ] - : []), + getUpdateAction({ + action, + label, + getCaseDetailHrefWithCommentId, + handleOutlineComment, + }), ]; - } else if (comment != null && comment.type === CommentType.generatedAlert) { - // TODO: clean this up - const alertIds = Array.isArray(comment.alertId) ? comment.alertId : [comment.alertId]; + } - if (isEmpty(alertIds)) { - return comments; + // Pushed information + if (action.actionField.length === 1 && action.actionField[0] === 'pushed') { + const parsedValue = parseString(`${action.newValue}`); + const { firstPush, parsedConnectorId, parsedConnectorName } = getPushInfo( + caseServices, + parsedValue, + index + ); + + const label = getPushedServiceLabelTitle(action, firstPush); + + const showTopFooter = + action.action === 'push-to-service' && + index === caseServices[parsedConnectorId]?.lastPushIndex; + + const showBottomFooter = + action.action === 'push-to-service' && + index === caseServices[parsedConnectorId]?.lastPushIndex && + caseServices[parsedConnectorId].hasDataToPush; + + let footers: EuiCommentProps[] = []; + + if (showTopFooter) { + footers = [ + ...footers, + { + username: '', + type: 'update', + event: i18n.ALREADY_PUSHED_TO_SERVICE(`${parsedConnectorName}`), + timelineIcon: 'sortUp', + 'data-test-subj': 'top-footer', + }, + ]; + } + + if (showBottomFooter) { + footers = [ + ...footers, + { + username: '', + type: 'update', + event: i18n.REQUIRED_UPDATE_TO_SERVICE(`${parsedConnectorName}`), + timelineIcon: 'sortDown', + 'data-test-subj': 'bottom-footer', + }, + ]; } return [ ...comments, - ...(getRuleDetailsHref != null - ? [ - getGeneratedAlertsAttachment({ - action, - alertIds, - getCaseDetailHrefWithCommentId, - getRuleDetailsHref, - onRuleDetailsClick, - renderInvestigateInTimelineActionComponent, - ruleId: comment.rule?.id ?? '', - ruleName: comment.rule?.name ?? i18n.UNKNOWN_RULE, - }), - ] - : []), + getUpdateAction({ + action, + label, + getCaseDetailHrefWithCommentId, + handleOutlineComment, + }), + ...footers, ]; } - } - // Connectors - if (action.actionField.length === 1 && action.actionField[0] === 'connector') { - const label = getConnectorLabelTitle({ action, connectors }); - return [ - ...comments, - getUpdateAction({ + // title, description, comment updates, tags + if ( + action.actionField.length === 1 && + ['title', 'description', 'comment', 'tags', 'status'].includes(action.actionField[0]) + ) { + const myField = action.actionField[0]; + const label: string | JSX.Element = getLabelTitle({ action, - label, - getCaseDetailHrefWithCommentId, - handleOutlineComment, - }), - ]; - } - - // Pushed information - if (action.actionField.length === 1 && action.actionField[0] === 'pushed') { - const parsedValue = parseString(`${action.newValue}`); - const { firstPush, parsedConnectorId, parsedConnectorName } = getPushInfo( - caseServices, - parsedValue, - index - ); + field: myField, + }); - const label = getPushedServiceLabelTitle(action, firstPush); - - const showTopFooter = - action.action === 'push-to-service' && - index === caseServices[parsedConnectorId]?.lastPushIndex; - - const showBottomFooter = - action.action === 'push-to-service' && - index === caseServices[parsedConnectorId]?.lastPushIndex && - caseServices[parsedConnectorId].hasDataToPush; - - let footers: EuiCommentProps[] = []; - - if (showTopFooter) { - footers = [ - ...footers, - { - username: '', - type: 'update', - event: i18n.ALREADY_PUSHED_TO_SERVICE(`${parsedConnectorName}`), - timelineIcon: 'sortUp', - 'data-test-subj': 'top-footer', - }, - ]; - } - - if (showBottomFooter) { - footers = [ - ...footers, - { - username: '', - type: 'update', - event: i18n.REQUIRED_UPDATE_TO_SERVICE(`${parsedConnectorName}`), - timelineIcon: 'sortDown', - 'data-test-subj': 'bottom-footer', - }, + return [ + ...comments, + getUpdateAction({ + action, + label, + getCaseDetailHrefWithCommentId, + handleOutlineComment, + }), ]; } - return [ - ...comments, - getUpdateAction({ - action, - label, - getCaseDetailHrefWithCommentId, - handleOutlineComment, - }), - ...footers, - ]; - } - - // title, description, comment updates, tags - if ( - action.actionField.length === 1 && - ['title', 'description', 'comment', 'tags', 'status'].includes(action.actionField[0]) - ) { - const myField = action.actionField[0]; - const label: string | JSX.Element = getLabelTitle({ - action, - field: myField, - }); - - return [ - ...comments, - getUpdateAction({ - action, - label, - getCaseDetailHrefWithCommentId, - handleOutlineComment, - }), - ]; - } - - return comments; - }, []), + return comments; + }, + [descriptionCommentListObj] + ), [ caseUserActions, + descriptionCommentListObj, caseData.comments, selectedOutlineCommentId, manageMarkdownEditIds, handleManageMarkdownEditId, handleSaveComment, getCaseDetailHrefWithCommentId, - userCanCrud, isLoadingIds, handleManageQuote, + userCanCrud, manualAlertsData, getRuleDetailsHref, loadingAlertData, @@ -527,7 +595,7 @@ export const UserActionTree = React.memo( const comments = [...userActions, ...bottomActions]; useEffect(() => { - console.error('commentDraft', storage.get('xpack.cases.commentDraft')); + // console.error('commentDraft', storage.get('xpack.cases.commentDraft')); const incomingEmbeddablePackage = embeddable .getStateTransfer() .getIncomingEmbeddablePackage('securitySolution:case'); @@ -535,13 +603,14 @@ export const UserActionTree = React.memo( if (storage.get('xpack.cases.commentDraft')) { try { draftComment = JSON.parse(storage.get('xpack.cases.commentDraft')); + // eslint-disable-next-line no-empty } catch (e) {} } - console.error('incomingEmbeddablePackage', incomingEmbeddablePackage, draftComment); + // console.error('incomingEmbeddablePackage', incomingEmbeddablePackage, draftComment); - if (true || incomingEmbeddablePackage) { - console.error('incomingEmbeddablePackage', incomingEmbeddablePackage); + if (incomingEmbeddablePackage) { + // console.error('incomingEmbeddablePackage', incomingEmbeddablePackage); if (draftComment) { if (!draftComment.commentId) { @@ -578,7 +647,7 @@ export const UserActionTree = React.memo( } }, [embeddable, manageMarkdownEditIds, storage]); - console.error('markdownEditorRefs', commentRefs.current); + // console.error('markdownEditorRefs', commentRefs.current); return ( <> diff --git a/x-pack/plugins/cases/public/types.ts b/x-pack/plugins/cases/public/types.ts index 2b31935c3ff97..5c7e57d0f7ffa 100644 --- a/x-pack/plugins/cases/public/types.ts +++ b/x-pack/plugins/cases/public/types.ts @@ -7,11 +7,15 @@ import { CoreStart } from 'kibana/public'; import { ReactElement } from 'react'; + import { SecurityPluginSetup } from '../../security/public'; -import { +import type { TriggersAndActionsUIPublicPluginSetup as TriggersActionsSetup, TriggersAndActionsUIPublicPluginStart as TriggersActionsStart, } from '../../triggers_actions_ui/public'; +import type { EmbeddableStart } from '../../../../src/plugins/embeddable/public'; +import type { Storage } from '../../../../src/plugins/kibana_utils/public'; + import { AllCasesProps } from './components/all_cases'; import { CaseViewProps } from './components/case_view'; import { ConfigureCasesProps } from './components/configure_cases'; @@ -25,6 +29,8 @@ export interface SetupPlugins { } export interface StartPlugins { + embeddable: EmbeddableStart; + storage: Storage; triggersActionsUi: TriggersActionsStart; } diff --git a/x-pack/plugins/cases/tsconfig.json b/x-pack/plugins/cases/tsconfig.json index 493fe6430efa7..8871031072747 100644 --- a/x-pack/plugins/cases/tsconfig.json +++ b/x-pack/plugins/cases/tsconfig.json @@ -17,6 +17,7 @@ { "path": "../../../src/core/tsconfig.json" }, // optionalPlugins from ./kibana.json + { "path": "../lens/tsconfig.json" }, { "path": "../security/tsconfig.json" }, { "path": "../spaces/tsconfig.json" }, diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index 8615dd69e1073..d2fe64938db74 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { AppMountParameters, CoreSetup, CoreStart, NavigateToAppOptions } from 'kibana/public'; +import { AppMountParameters, CoreSetup, CoreStart } from 'kibana/public'; import type { Start as InspectorStartContract } from 'src/plugins/inspector/public'; import { UsageCollectionSetup, UsageCollectionStart } from 'src/plugins/usage_collection/public'; import { DataPublicPluginSetup, DataPublicPluginStart } from '../../../../src/plugins/data/public'; @@ -113,7 +113,14 @@ export interface LensPublicStart { * * @experimental */ - navigateToPrefilledEditor: (input: LensEmbeddableInput, options?: NavigateToAppOptions) => void; + navigateToPrefilledEditor: ( + input: LensEmbeddableInput, + options?: { + openInNewTab?: boolean; + originatingApp?: string; + originatingPath?: string; + } + ) => void; /** * Method which returns true if the user has permission to use Lens as defined by application capabilities. */ @@ -260,14 +267,7 @@ export class LensPlugin { return { EmbeddableComponent: getEmbeddableComponent(core, startDependencies), SaveModalComponent: getSaveModalComponent(core, startDependencies, this.attributeService!), - navigateToPrefilledEditor: ( - input: LensEmbeddableInput, - options?: { - openInNewTab?: boolean; - originatingApp?: string; - originatingPath?: string; - } - ) => { + navigateToPrefilledEditor: (input, options) => { // for openInNewTab, we set the time range in url via getEditPath below if (input.timeRange && !options?.openInNewTab) { startDependencies.data.query.timefilter.timefilter.setTime(input.timeRange); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx index dbe9cd163451d..ee30b99e90d6f 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx @@ -69,7 +69,9 @@ export function ExploratoryViewHeader({ seriesId, lensAttributes }: Props) { timeRange, attributes: lensAttributes, }, - true + { + openInNewTab: true, + } ); } }} diff --git a/x-pack/plugins/security_solution/kibana.json b/x-pack/plugins/security_solution/kibana.json index 1376f7df51003..969a233686d39 100644 --- a/x-pack/plugins/security_solution/kibana.json +++ b/x-pack/plugins/security_solution/kibana.json @@ -15,6 +15,7 @@ "features", "taskManager", "inspector", + "lens", "licensing", "maps", "timelines", @@ -23,7 +24,6 @@ ], "optionalPlugins": [ "encryptedSavedObjects", - "lens", "fleet", "ml", "newsfeed", diff --git a/x-pack/plugins/security_solution/public/cases/components/create/index.tsx b/x-pack/plugins/security_solution/public/cases/components/create/index.tsx index dfd53ae5cc0b0..41e94d283ea6e 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/create/index.tsx @@ -11,6 +11,7 @@ import { EuiPanel } from '@elastic/eui'; import { getCaseDetailsUrl, getCaseUrl } from '../../../common/components/link_to'; import { useKibana } from '../../../common/lib/kibana'; import * as timelineMarkdownPlugin from '../../../common/components/markdown_editor/plugins/timeline'; +import * as lensMarkdownPlugin from '../../../common/components/markdown_editor/plugins/lens'; import { useInsertTimeline } from '../use_insert_timeline'; import { APP_ID, CASES_APP_ID } from '../../../../common/constants'; import { useGetUrlSearch } from '../../../common/components/navigation/use_get_url_search'; @@ -42,6 +43,14 @@ export const Create = React.memo(() => { {cases.getCreateCase({ onCancel: handleSetIsCancel, onSuccess, + lensIntegration: { + editor_context: lensMarkdownPlugin.context, + editor_plugins: { + parsingPlugin: lensMarkdownPlugin.parser, + processingPluginRenderer: lensMarkdownPlugin.renderer, + uiPlugin: lensMarkdownPlugin.plugin, + }, + }, timelineIntegration: { editor_plugins: { parsingPlugin: timelineMarkdownPlugin.parser, diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/editor.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/editor.tsx index 12084a17e888a..94280e4c1a41f 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/editor.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/editor.tsx @@ -5,8 +5,18 @@ * 2.0. */ -import React, { memo, useEffect, useState, useCallback } from 'react'; +import React, { + forwardRef, + memo, + useEffect, + useImperativeHandle, + useRef, + useState, + useCallback, + ElementRef, +} from 'react'; import { EuiMarkdownEditor } from '@elastic/eui'; +import { ContextShape } from '@elastic/eui/src/components/markdown_editor/markdown_context'; import { uiPlugins, parsingPlugins, processingPlugins } from './plugins'; @@ -19,39 +29,59 @@ interface MarkdownEditorProps { height?: number; } -const MarkdownEditorComponent: React.FC = ({ - onChange, - value, - ariaLabel, - editorId, - dataTestSubj, - height, -}) => { - const [markdownErrorMessages, setMarkdownErrorMessages] = useState([]); - const onParse = useCallback((err, { messages }) => { - setMarkdownErrorMessages(err ? [err] : messages); - }, []); - - useEffect( - () => document.querySelector('textarea.euiMarkdownEditorTextArea')?.focus(), - [] - ); - - return ( - - ); -}; +type EuiMarkdownEditorRef = ElementRef; + +export interface MarkdownEditorRef { + textarea: HTMLTextAreaElement | null; + replaceNode: ContextShape['replaceNode']; + toolbar: HTMLDivElement | null; +} + +const MarkdownEditorComponent = forwardRef( + ({ onChange, value, ariaLabel, editorId, dataTestSubj, height }, ref) => { + const [markdownErrorMessages, setMarkdownErrorMessages] = useState([]); + const onParse = useCallback((err, { messages }) => { + setMarkdownErrorMessages(err ? [err] : messages); + }, []); + const editorRef = useRef(null); + + useEffect(() => editorRef.current?.textarea?.focus(), []); + + // @ts-expect-error update types + useImperativeHandle(ref, () => { + console.error('reft2222', ref, editorRef); + + if (!editorRef.current) { + return null; + } + + const editorNode = editorRef.current?.textarea?.closest('.euiMarkdownEditor'); + + return { + ...editorRef.current, + toolbar: editorNode?.querySelector('.euiMarkdownEditorToolbar'), + }; + }); + + return ( + + ); + } +); + +MarkdownEditorComponent.displayName = 'MarkdownEditorComponent'; export const MarkdownEditor = memo(MarkdownEditorComponent); diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/eui_form.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/eui_form.tsx index 1c407b3b8f8c2..82e4d5d5a2600 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/eui_form.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/eui_form.tsx @@ -5,12 +5,12 @@ * 2.0. */ -import React from 'react'; +import React, { forwardRef } from 'react'; import styled from 'styled-components'; import { EuiMarkdownEditorProps, EuiFormRow, EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; import { FieldHook, getFieldValidityAndErrorMessage } from '../../../shared_imports'; -import { MarkdownEditor } from './editor'; +import { MarkdownEditor, MarkdownEditorRef } from './editor'; type MarkdownEditorFormProps = EuiMarkdownEditorProps & { id: string; @@ -27,40 +27,41 @@ const BottomContentWrapper = styled(EuiFlexGroup)` `} `; -export const MarkdownEditorForm: React.FC = ({ - id, - field, - dataTestSubj, - idAria, - bottomRightContent, -}) => { - const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); +export const MarkdownEditorForm = React.memo( + forwardRef( + ({ id, field, dataTestSubj, idAria, bottomRightContent }, ref) => { + const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); - return ( - - <> - - {bottomRightContent && ( - - {bottomRightContent} - - )} - - - ); -}; + return ( + + <> + + {bottomRightContent && ( + + {bottomRightContent} + + )} + + + ); + } + ) +); + +MarkdownEditorForm.displayName = 'MarkdownEditorForm'; diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/context.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/context.tsx index 0f2b7e550be18..d7f5b0612cb73 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/context.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/context.tsx @@ -7,4 +7,7 @@ import React from 'react'; -export const CommentEditorContext = React.createContext(null); +export const CommentEditorContext = React.createContext<{ + editorId: string; + value: string; +} | null>(null); diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/helpers.ts b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/helpers.ts index 45fd51ad443a1..05d789d515eeb 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/helpers.ts +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/helpers.ts @@ -15,7 +15,7 @@ import type { // Generate a Lens state based on some app-specific input parameters. // `TypedLensByValueInput` can be used for type-safety - it uses the same interfaces as Lens-internal code. export const getLensAttributes = ( - defaultIndexPattern: IndexPattern + defaultIndexPattern: IndexPattern | null ): TypedLensByValueInput['attributes'] => { const dataLayer: PersistedIndexPatternLayer = { columnOrder: ['col1', 'col2'], @@ -35,7 +35,7 @@ export const getLensAttributes = ( operationType: 'date_histogram', params: { interval: 'auto' }, scale: 'interval', - sourceField: defaultIndexPattern.timeFieldName!, + sourceField: defaultIndexPattern?.timeFieldName!, }, }, }; @@ -64,12 +64,12 @@ export const getLensAttributes = ( title: '', references: [ { - id: defaultIndexPattern.id!, + id: defaultIndexPattern?.id!, name: 'indexpattern-datasource-current-indexpattern', type: 'index-pattern', }, { - id: defaultIndexPattern.id!, + id: defaultIndexPattern?.id!, name: 'indexpattern-datasource-layer-layer1', type: 'index-pattern', }, diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx index f077ca4d46d31..ef0f450e2d053 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx @@ -5,14 +5,31 @@ * 2.0. */ -import { EuiModal, EuiModalBody, EuiModalHeader, EuiModalHeaderTitle } from '@elastic/eui'; +import { + EuiModal, + EuiModalProps, + EuiModalBody, + EuiModalHeader, + EuiModalHeaderTitle, +} from '@elastic/eui'; import React from 'react'; -import { SavedObjectFinderUi } from '../../../../../../../../../src/plugins/saved_objects/public'; +import { + SavedObjectFinderUi, + SavedObjectFinderUiProps, +} from '../../../../../../../../../src/plugins/saved_objects/public'; import { useKibana } from '../../../../lib/kibana'; import { ModalContainer } from './modal_container'; -const LensSavedObjectsModalComponent = ({ onClose, onChoose }) => { +interface LensSavedObjectsModalProps { + onClose: EuiModalProps['onClose']; + onChoose: SavedObjectFinderUiProps['onChoose']; +} + +const LensSavedObjectsModalComponent: React.FC = ({ + onClose, + onChoose, +}) => { const { savedObjects, uiSettings } = useKibana().services; return ( diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx index 921ed627fafa1..9dd484e5538aa 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx @@ -5,8 +5,6 @@ * 2.0. */ -import dateMath from '@elastic/datemath'; - import { EuiFieldText, EuiModalBody, @@ -22,13 +20,13 @@ import { EuiFlexItem, EuiFlexGroup, EuiFormRow, + EuiMarkdownAstNodePosition, } from '@elastic/eui'; -// @ts-expect-error update types -import { insertText } from '@elastic/eui/lib/components/markdown_editor/markdown_actions'; -import React, { useCallback, useContext, useMemo, useRef, useEffect, useState } from 'react'; -import moment, { Moment } from 'moment'; +import React, { useCallback, useContext, useMemo, useEffect, useState } from 'react'; +import moment from 'moment'; import { useLocation } from 'react-router-dom'; +import type { TypedLensByValueInput } from '../../../../../../../lens/public'; import { useKibana } from '../../../../lib/kibana'; import { LensMarkDownRenderer } from './processor'; import { ID } from './constants'; @@ -37,23 +35,34 @@ import { CommentEditorContext } from './context'; import { LensSavedObjectsModal } from './lens_saved_objects_modal'; import { ModalContainer } from './modal_container'; import { getLensAttributes } from './helpers'; +import type { + EmbeddablePackageState, + EmbeddableInput, +} from '../../../../../../../../../src/plugins/embeddable/public'; + +type LensIncomingEmbeddablePackage = Omit & { + input: Omit & { + id: string | undefined; + attributes: TypedLensByValueInput['attributes']; + }; +}; -interface LensEditorProps { - editMode?: boolean | null; - id?: string | null; - title?: string | null; - startDate?: Moment | null; - endDate?: Moment | null; - onClosePopover: () => void; - onSave: (markdown: string, config: { block: boolean }) => void; -} +type LensEuiMarkdownEditorUiPlugin = EuiMarkdownEditorUiPlugin<{ + title: string; + startDate: string; + endDate: string; + position: EuiMarkdownAstNodePosition; + attributes: TypedLensByValueInput['attributes']; +}>; -const LensEditorComponent: React.FC = ({ node, onClosePopover, onSave }) => { +const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ + node, + onCancel, + onSave, +}) => { const location = useLocation(); const { embeddable, - savedObjects, - uiSettings, lens, storage, data: { @@ -62,33 +71,23 @@ const LensEditorComponent: React.FC = ({ node, onClosePopover, timefilter: { timefilter }, }, }, - ...rest } = useKibana().services; const [lensEmbeddableAttributes, setLensEmbeddableAttributes] = useState( node?.attributes ?? null ); - const [startDate, setStartDate] = useState( - node?.startDate ? moment(node.startDate) : moment().subtract(7, 'd') + const [startDate, setStartDate] = useState( + node?.startDate ? moment(node.startDate).format() : 'now-7d' ); - const [endDate, setEndDate] = useState( - node?.endDate ? moment(node.endDate) : moment() + const [endDate, setEndDate] = useState( + node?.endDate ? moment(node.endDate).format() : 'now' ); const [lensTitle, setLensTitle] = useState(node?.title ?? ''); const [showLensSavedObjectsModal, setShowLensSavedObjectsModal] = useState(false); const commentEditorContext = useContext(CommentEditorContext); const markdownContext = useContext(EuiMarkdownContext); - console.error('contextcontextcontext', commentEditorContext); - - console.error('rest', timefilter.getTime(), rest); - - const handleLensDateChange = useCallback((data) => { - if (data.range?.length === 2) { - setStartDate(moment(data.range[0])); - setEndDate(moment(data.range[1])); - } - }, []); + // console.error('contextcontextcontext', commentEditorContext); const handleTitleChange = useCallback((e) => { setLensTitle(e.target.value); @@ -99,6 +98,7 @@ const LensEditorComponent: React.FC = ({ node, onClosePopover, if (storage.get('xpack.cases.commentDraft')) { try { draftComment = JSON.parse(storage.get('xpack.cases.commentDraft')); + // eslint-disable-next-line no-empty } catch (e) {} } @@ -112,7 +112,7 @@ const LensEditorComponent: React.FC = ({ node, onClosePopover, attributes: lensEmbeddableAttributes, })}}` ); - onClosePopover(); + onCancel(); return; } @@ -134,7 +134,7 @@ const LensEditorComponent: React.FC = ({ node, onClosePopover, node, lensEmbeddableAttributes, markdownContext, - onClosePopover, + onCancel, onSave, startDate, endDate, @@ -161,9 +161,9 @@ const LensEditorComponent: React.FC = ({ node, onClosePopover, { id: '', timeRange: { - from: lensEmbeddableAttributes ? startDate : 'now-5d', - to: lensEmbeddableAttributes ? endDate : 'now', - mode: startDate ? 'absolute' : 'relative', + from: (lensEmbeddableAttributes && startDate) ?? 'now-5d', + to: (lensEmbeddableAttributes && endDate) ?? 'now', + mode: lensEmbeddableAttributes ? 'absolute' : 'relative', }, attributes: lensEmbeddableAttributes ?? getLensAttributes(await indexPatterns.getDefault()), }, @@ -186,10 +186,25 @@ const LensEditorComponent: React.FC = ({ node, onClosePopover, originatingPath, ]); + const handleChooseLensSO = useCallback( + (savedObjectId, savedObjectType, fullName, savedObject) => { + // console.error('apya', savedObjectId, savedObjectType, fullName, savedObject); + setLensEmbeddableAttributes({ + ...savedObject.attributes, + title: '', + references: savedObject.references, + }); + setShowLensSavedObjectsModal(false); + }, + [] + ); + + const handleCloseLensSOModal = useCallback(() => setShowLensSavedObjectsModal(false), []); + useEffect(() => { const incomingEmbeddablePackage = embeddable .getStateTransfer() - .getIncomingEmbeddablePackage('securitySolution:case', true); + .getIncomingEmbeddablePackage('securitySolution:case', true) as LensIncomingEmbeddablePackage; if ( incomingEmbeddablePackage?.type === 'lens' && @@ -198,8 +213,8 @@ const LensEditorComponent: React.FC = ({ node, onClosePopover, setLensEmbeddableAttributes(incomingEmbeddablePackage?.input.attributes); const lensTime = timefilter.getTime(); if (lensTime?.from && lensTime?.to) { - setStartDate(dateMath.parse(lensTime.from)!); - setEndDate(dateMath.parse(lensTime.to)!); + setStartDate(lensTime.from); + setEndDate(lensTime.to); } } @@ -207,21 +222,19 @@ const LensEditorComponent: React.FC = ({ node, onClosePopover, if (storage.get('xpack.cases.commentDraft')) { try { draftComment = JSON.parse(storage.get('xpack.cases.commentDraft')); + if (draftComment.title) { + setLensTitle(draftComment.title); + } + // eslint-disable-next-line no-empty } catch (e) {} } - if (draftComment.title) { - setLensTitle(draftComment.title); - } - - console.error('stoargesgeet', storage.get('xpack.cases.commentDraft')); + // console.error('stoargesgeet', storage.get('xpack.cases.commentDraft')); }, [embeddable, storage, timefilter]); - console.error('insertText', insertText); + // console.error('markdownContext', markdownContext); - console.error('markdownContext', markdownContext); - - console.error('lensEmbeddableAttributes', lensEmbeddableAttributes); + // console.error('lensEmbeddableAttributes', lensEmbeddableAttributes); return ( <> @@ -270,7 +283,7 @@ const LensEditorComponent: React.FC = ({ node, onClosePopover, {`Edit in Lens`} @@ -281,39 +294,28 @@ const LensEditorComponent: React.FC = ({ node, onClosePopover,
- {'Cancel'} + {'Cancel'} {'Add to a Case'}
{showLensSavedObjectsModal ? ( - setShowLensSavedObjectsModal(false)} - onChoose={(savedObjectId, savedObjectType, fullName, savedObject) => { - console.error('apya', savedObjectId, savedObjectType, fullName, savedObject); - setLensEmbeddableAttributes({ - ...savedObject.attributes, - references: savedObject.references, - }); - setShowLensSavedObjectsModal(false); - }} - /> + ) : null} ); }; -const LensEditor = React.memo(LensEditorComponent); +export const LensEditor = React.memo(LensEditorComponent); -export const plugin: EuiMarkdownEditorUiPlugin = { +export const plugin: LensEuiMarkdownEditorUiPlugin = { name: ID, button: { label: i18n.INSERT_LENS, @@ -321,10 +323,8 @@ export const plugin: EuiMarkdownEditorUiPlugin = { }, helpText: ( - {'{lens!{}}'} + {'!{lens}'} ), - editor: function editor({ node, onSave, onCancel, ...rest }) { - return ; - }, + editor: LensEditor, }; diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/processor.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/processor.tsx index 59708f673d1b5..52cc7043b4c42 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/processor.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/processor.tsx @@ -5,13 +5,13 @@ * 2.0. */ -import React from 'react'; +import React, { useCallback } from 'react'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiText, EuiSpacer } from '@elastic/eui'; import styled from 'styled-components'; import { useLocation } from 'react-router-dom'; import { createGlobalStyle } from '../../../../../../../../../src/plugins/kibana_react/common'; -import { EmbeddableComponentProps, TypedLensByValueInput } from '../../../../../../../lens/public'; +import { TypedLensByValueInput } from '../../../../../../../lens/public'; import { useKibana } from '../../../../lib/kibana'; import { LENS_VISUALIZATION_HEIGHT } from './constants'; @@ -32,7 +32,6 @@ interface LensMarkDownRendererProps { title?: string | null; startDate?: string | null; endDate?: string | null; - onBrushEnd?: EmbeddableComponentProps['onBrushEnd']; viewMode?: boolean | undefined; } @@ -41,7 +40,6 @@ const LensMarkDownRendererComponent: React.FC = ({ title, startDate, endDate, - onBrushEnd, viewMode = true, }) => { const location = useLocation(); @@ -51,9 +49,38 @@ const LensMarkDownRendererComponent: React.FC = ({ canUseEditor, } = useKibana().services.lens; - console.error('loaa', location); + // console.error('loaa', location); + + // console.error('sss', attributes, canUseEditor()); + + const handleClick = useCallback(() => { + if (attributes) { + navigateToPrefilledEditor( + { + id: '', + timeRange: { + from: startDate ?? 'now-7d', + to: endDate ?? 'now', + mode: startDate ? 'absolute' : 'relative', + }, + attributes, + }, + { + openInNewTab: true, + // originatingApp: 'securitySolution:case', + // originatingPath: `${location.pathname}${location.search}`, + } + ); + } + }, [ + attributes, + endDate, + location.pathname, + location.search, + navigateToPrefilledEditor, + startDate, + ]); - console.error('sss', attributes, canUseEditor()); return ( {attributes ? ( @@ -69,26 +96,8 @@ const LensMarkDownRendererComponent: React.FC = ({ { - if (attributes) { - navigateToPrefilledEditor( - { - id: '', - timeRange: { - from: startDate ?? 'now-5d', - to: endDate ?? 'now', - mode: startDate ? 'absolute' : 'relative', - }, - attributes, - }, - { - originatingApp: 'securitySolution:case', - originatingPath: `${location.pathname}${location.search}`, - } - ); - } - }} + isDisabled={!canUseEditor() || !attributes} + onClick={handleClick} > {`Open in Lens`} @@ -103,12 +112,11 @@ const LensMarkDownRendererComponent: React.FC = ({ id="" style={{ height: LENS_VISUALIZATION_HEIGHT }} timeRange={{ - from: startDate ?? 'now-5d', + from: startDate ?? 'now-7d', to: endDate ?? 'now', mode: startDate ? 'absolute' : 'relative', }} attributes={attributes} - onBrushEnd={onBrushEnd} /> ) : null} diff --git a/x-pack/plugins/security_solution/public/types.ts b/x-pack/plugins/security_solution/public/types.ts index 304a757f3d313..6337e6d53c3bc 100644 --- a/x-pack/plugins/security_solution/public/types.ts +++ b/x-pack/plugins/security_solution/public/types.ts @@ -54,7 +54,7 @@ export interface StartPlugins { embeddable: EmbeddableStart; inspector: InspectorStart; fleet?: FleetStart; - lens?: LensPublicStart; + lens: LensPublicStart; lists?: ListsPluginStart; licensing: LicensingPluginStart; newsfeed?: NewsfeedPublicPluginStart; From 1c8fbbfdef910dfe1c04509c5795a4c72314fa2c Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Sun, 27 Jun 2021 22:08:37 +0200 Subject: [PATCH 13/41] cleanup --- x-pack/plugins/cases/kibana.json | 4 ++-- .../public/components/add_comment/index.tsx | 4 +++- .../components/markdown_editor/editor.tsx | 3 --- .../components/user_action_tree/index.tsx | 14 +++++------- x-pack/plugins/observability/kibana.json | 1 + .../components/markdown_editor/editor.tsx | 2 +- .../markdown_editor/plugins/lens/plugin.tsx | 22 ++++++++++++++++--- .../plugins/lens/processor.tsx | 20 +++++++++-------- 8 files changed, 42 insertions(+), 28 deletions(-) diff --git a/x-pack/plugins/cases/kibana.json b/x-pack/plugins/cases/kibana.json index 6439f28b958d0..f72f0e012bd80 100644 --- a/x-pack/plugins/cases/kibana.json +++ b/x-pack/plugins/cases/kibana.json @@ -1,7 +1,7 @@ { "configPath":[ - "cases", - "xpack" + "xpack", + "cases" ], "description":"The Case management system in Kibana", "extraPublicDirs":[ diff --git a/x-pack/plugins/cases/public/components/add_comment/index.tsx b/x-pack/plugins/cases/public/components/add_comment/index.tsx index a38ac1918e227..e1f57440c188a 100644 --- a/x-pack/plugins/cases/public/components/add_comment/index.tsx +++ b/x-pack/plugins/cases/public/components/add_comment/index.tsx @@ -36,6 +36,7 @@ export interface AddCommentRefObject { } export interface AddCommentProps { + id: string; caseId: string; userCanCrud?: boolean; onCommentSaving?: () => void; @@ -47,7 +48,7 @@ export interface AddCommentProps { export const AddComment = React.memo( forwardRef( ( - { caseId, userCanCrud, onCommentPosted, onCommentSaving, showLoading = true, subCaseId }, + { id, caseId, userCanCrud, onCommentPosted, onCommentSaving, showLoading = true, subCaseId }, ref ) => { const editorRef = useRef(); @@ -110,6 +111,7 @@ export const AddComment = React.memo( component={MarkdownEditorForm} componentProps={{ ref: editorRef, + id, idAria: 'caseComment', isDisabled: isLoading, dataTestSubj: 'add-comment', diff --git a/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx b/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx index a7862baaf4d28..3c217a04dbace 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx @@ -53,8 +53,6 @@ const MarkdownEditorComponent = forwardRef { - console.error('reft2222', ref, editorRef); - if (!editorRef.current) { return null; } @@ -89,7 +87,6 @@ const MarkdownEditorComponent = forwardRef diff --git a/x-pack/plugins/cases/public/components/user_action_tree/index.tsx b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx index b142471c90897..f245e29271e41 100644 --- a/x-pack/plugins/cases/public/components/user_action_tree/index.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx @@ -226,6 +226,7 @@ export const UserActionTree = React.memo( const MarkdownDescription = useMemo( () => ( (commentRefs.current[DESCRIPTION_ID] = element)} id={DESCRIPTION_ID} content={caseData.description} isEditable={manageMarkdownEditIds.includes(DESCRIPTION_ID)} @@ -241,6 +242,7 @@ export const UserActionTree = React.memo( const MarkdownNewComment = useMemo( () => ( (commentRefs.current[NEW_ID] = element)} @@ -595,7 +597,6 @@ export const UserActionTree = React.memo( const comments = [...userActions, ...bottomActions]; useEffect(() => { - // console.error('commentDraft', storage.get('xpack.cases.commentDraft')); const incomingEmbeddablePackage = embeddable .getStateTransfer() .getIncomingEmbeddablePackage('securitySolution:case'); @@ -607,19 +608,16 @@ export const UserActionTree = React.memo( } catch (e) {} } - // console.error('incomingEmbeddablePackage', incomingEmbeddablePackage, draftComment); - if (incomingEmbeddablePackage) { - // console.error('incomingEmbeddablePackage', incomingEmbeddablePackage); - if (draftComment) { if (!draftComment.commentId) { if (commentRefs.current && commentRefs.current[NEW_ID]) { commentRefs.current[NEW_ID].setComment(draftComment.comment); - const buttons = commentRefs.current[NEW_ID].toolbar?.querySelector( + const buttons = commentRefs.current[NEW_ID].editor.toolbar?.querySelector( '[aria-label="Insert lens link"]' ); - buttons.click(); + buttons?.click(); + return; } } @@ -647,8 +645,6 @@ export const UserActionTree = React.memo( } }, [embeddable, manageMarkdownEditIds, storage]); - // console.error('markdownEditorRefs', commentRefs.current); - return ( <> diff --git a/x-pack/plugins/observability/kibana.json b/x-pack/plugins/observability/kibana.json index d13140f0be16c..29975a7ad05a4 100644 --- a/x-pack/plugins/observability/kibana.json +++ b/x-pack/plugins/observability/kibana.json @@ -16,6 +16,7 @@ "alerting", "cases", "data", + "embeddable", "features", "ruleRegistry", "triggersActionsUi" diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/editor.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/editor.tsx index 94280e4c1a41f..ad36042030ae4 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/editor.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/editor.tsx @@ -49,7 +49,7 @@ const MarkdownEditorComponent = forwardRef { - console.error('reft2222', ref, editorRef); + // console.error('reft2222', ref, editorRef); if (!editorRef.current) { return null; diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx index 9dd484e5538aa..f3d843c00d11a 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx @@ -5,6 +5,8 @@ * 2.0. */ +import { first } from 'rxjs/operators'; + import { EuiFieldText, EuiModalBody, @@ -60,8 +62,10 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ onCancel, onSave, }) => { + const srvics = useKibana(); const location = useLocation(); const { + application: { currentAppId$ }, embeddable, lens, storage, @@ -73,6 +77,9 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ }, } = useKibana().services; + const currentAppId = useRef(null); + + const [editMode, setEditMode] = useState(!!node); const [lensEmbeddableAttributes, setLensEmbeddableAttributes] = useState( node?.attributes ?? null ); @@ -102,7 +109,7 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ } catch (e) {} } - if (node) { + if (!node && draftComment) { markdownContext.replaceNode( draftComment.position, `!{lens${JSON.stringify({ @@ -188,7 +195,7 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ const handleChooseLensSO = useCallback( (savedObjectId, savedObjectType, fullName, savedObject) => { - // console.error('apya', savedObjectId, savedObjectType, fullName, savedObject); + console.error('apya', savedObjectId, savedObjectType, fullName, savedObject); setLensEmbeddableAttributes({ ...savedObject.attributes, title: '', @@ -225,6 +232,7 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ if (draftComment.title) { setLensTitle(draftComment.title); } + setEditMode(true); // eslint-disable-next-line no-empty } catch (e) {} } @@ -232,6 +240,14 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ // console.error('stoargesgeet', storage.get('xpack.cases.commentDraft')); }, [embeddable, storage, timefilter]); + useEffect(() => { + const getCurrentAppId = async () => { + const appId = await currentAppId$.pipe(first()).toPromise(); + currentAppId.current = appId; + }; + getCurrentAppId(); + }, [currentAppId$]); + // console.error('markdownContext', markdownContext); // console.error('lensEmbeddableAttributes', lensEmbeddableAttributes); @@ -302,7 +318,7 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ {'Cancel'} - {'Add to a Case'} + {editMode ? 'Update' : 'Add to a Case'} diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/processor.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/processor.tsx index 52cc7043b4c42..ce210964576c8 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/processor.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/processor.tsx @@ -49,11 +49,16 @@ const LensMarkDownRendererComponent: React.FC = ({ canUseEditor, } = useKibana().services.lens; - // console.error('loaa', location); - - // console.error('sss', attributes, canUseEditor()); - const handleClick = useCallback(() => { + const options = viewMode + ? { + openInNewTab: true, + } + : { + originatingApp: 'securitySolution:case', + originatingPath: `${location.pathname}${location.search}`, + }; + if (attributes) { navigateToPrefilledEditor( { @@ -65,11 +70,7 @@ const LensMarkDownRendererComponent: React.FC = ({ }, attributes, }, - { - openInNewTab: true, - // originatingApp: 'securitySolution:case', - // originatingPath: `${location.pathname}${location.search}`, - } + options ); } }, [ @@ -79,6 +80,7 @@ const LensMarkDownRendererComponent: React.FC = ({ location.search, navigateToPrefilledEditor, startDate, + viewMode, ]); return ( From 8ab3b71704decf6d9a33136386ecb98aea560685 Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Mon, 28 Jun 2021 00:31:05 +0200 Subject: [PATCH 14/41] currentAppId --- .../components/add_comment/index.test.tsx | 1 + .../components/user_action_tree/index.tsx | 41 +++++++++---- .../markdown_editor/plugins/index.ts | 6 +- .../markdown_editor/plugins/lens/plugin.tsx | 60 ++++++++++--------- .../plugins/lens/processor.tsx | 22 +++++-- 5 files changed, 79 insertions(+), 51 deletions(-) diff --git a/x-pack/plugins/cases/public/components/add_comment/index.test.tsx b/x-pack/plugins/cases/public/components/add_comment/index.test.tsx index 078db1e6dbe6d..43c7f8e5ba4b8 100644 --- a/x-pack/plugins/cases/public/components/add_comment/index.test.tsx +++ b/x-pack/plugins/cases/public/components/add_comment/index.test.tsx @@ -26,6 +26,7 @@ const onCommentPosted = jest.fn(); const postComment = jest.fn(); const addCommentProps: AddCommentProps = { + id: 'newComment', caseId: '1234', userCanCrud: true, onCommentSaving, diff --git a/x-pack/plugins/cases/public/components/user_action_tree/index.tsx b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx index f245e29271e41..a5972a29511ff 100644 --- a/x-pack/plugins/cases/public/components/user_action_tree/index.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import { first } from 'rxjs/operators'; import { EuiFlexGroup, EuiFlexItem, @@ -135,7 +136,11 @@ export const UserActionTree = React.memo( userCanCrud, }: UserActionTreeProps) => { const commentRefs = useRef>({}); - const { embeddable, storage } = useKibana().services; + const { + application: { currentAppId$ }, + embeddable, + storage, + } = useKibana().services; const { detailName: caseId, commentId, subCaseId } = useParams<{ detailName: string; commentId?: string; @@ -146,6 +151,7 @@ export const UserActionTree = React.memo( const [selectedOutlineCommentId, setSelectedOutlineCommentId] = useState(''); const { isLoadingIds, patchComment } = useUpdateComment(); const currentUser = useCurrentUser(); + const [currentAppId, setCurrentAppId] = useState(null); const [manageMarkdownEditIds, setManageMarkdownEditIds] = useState([]); const [loadingAlertData, manualAlertsData] = useFetchAlertData( @@ -597,18 +603,31 @@ export const UserActionTree = React.memo( const comments = [...userActions, ...bottomActions]; useEffect(() => { - const incomingEmbeddablePackage = embeddable - .getStateTransfer() - .getIncomingEmbeddablePackage('securitySolution:case'); - let draftComment; - if (storage.get('xpack.cases.commentDraft')) { - try { - draftComment = JSON.parse(storage.get('xpack.cases.commentDraft')); - // eslint-disable-next-line no-empty - } catch (e) {} + const getCurrentAppId = async () => { + const appId = await currentAppId$.pipe(first()).toPromise(); + setCurrentAppId(appId); + }; + getCurrentAppId(); + }, [currentAppId$]); + + useEffect(() => { + let incomingEmbeddablePackage; + + if (currentAppId) { + incomingEmbeddablePackage = embeddable + ?.getStateTransfer() + .getIncomingEmbeddablePackage(currentAppId); } if (incomingEmbeddablePackage) { + let draftComment; + if (storage.get('xpack.cases.commentDraft')) { + try { + draftComment = JSON.parse(storage.get('xpack.cases.commentDraft')); + // eslint-disable-next-line no-empty + } catch (e) {} + } + if (draftComment) { if (!draftComment.commentId) { if (commentRefs.current && commentRefs.current[NEW_ID]) { @@ -643,7 +662,7 @@ export const UserActionTree = React.memo( } } } - }, [embeddable, manageMarkdownEditIds, storage]); + }, [currentAppId, embeddable, manageMarkdownEditIds, storage]); return ( <> diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/index.ts b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/index.ts index 0aef0978a4a77..d5846a9f9ea50 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/index.ts +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/index.ts @@ -18,7 +18,6 @@ import { FunctionComponent } from 'react'; import rehype2react from 'rehype-react'; import { Plugin, PluggableList } from 'unified'; import * as timelineMarkdownPlugin from './timeline'; -import * as lensMarkdownPlugin from './lens'; export const { uiPlugins, parsingPlugins, processingPlugins } = { uiPlugins: getDefaultEuiMarkdownUiPlugins(), @@ -28,7 +27,7 @@ export const { uiPlugins, parsingPlugins, processingPlugins } = { [ typeof rehype2react, Parameters[0] & { - components: { a: FunctionComponent; timeline: unknown; lens: unknown }; + components: { a: FunctionComponent; timeline: unknown }; } ], ...PluggableList @@ -36,11 +35,8 @@ export const { uiPlugins, parsingPlugins, processingPlugins } = { }; uiPlugins.push(timelineMarkdownPlugin.plugin); -uiPlugins.push(lensMarkdownPlugin.plugin); parsingPlugins.push(timelineMarkdownPlugin.parser); -parsingPlugins.push(lensMarkdownPlugin.parser); // This line of code is TS-compatible and it will break if [1][1] change in the future. processingPlugins[1][1].components.timeline = timelineMarkdownPlugin.renderer; -processingPlugins[1][1].components.lens = lensMarkdownPlugin.renderer; diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx index f3d843c00d11a..8cdbdf9d45800 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx @@ -6,7 +6,6 @@ */ import { first } from 'rxjs/operators'; - import { EuiFieldText, EuiModalBody, @@ -62,7 +61,6 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ onCancel, onSave, }) => { - const srvics = useKibana(); const location = useLocation(); const { application: { currentAppId$ }, @@ -77,7 +75,7 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ }, } = useKibana().services; - const currentAppId = useRef(null); + const [currentAppId, setCurrentAppId] = useState(undefined); const [editMode, setEditMode] = useState(!!node); const [lensEmbeddableAttributes, setLensEmbeddableAttributes] = useState( @@ -94,8 +92,6 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ const commentEditorContext = useContext(CommentEditorContext); const markdownContext = useContext(EuiMarkdownContext); - // console.error('contextcontextcontext', commentEditorContext); - const handleTitleChange = useCallback((e) => { setLensTitle(e.target.value); }, []); @@ -109,7 +105,12 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ } catch (e) {} } - if (!node && draftComment) { + if (currentAppId) { + // clears Lens incoming state + embeddable?.getStateTransfer().getIncomingEmbeddablePackage(currentAppId, true); + } + + if (!node && draftComment.position) { markdownContext.replaceNode( draftComment.position, `!{lens${JSON.stringify({ @@ -119,6 +120,7 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ attributes: lensEmbeddableAttributes, })}}` ); + onCancel(); return; } @@ -138,14 +140,16 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ } }, [ storage, + currentAppId, node, lensEmbeddableAttributes, + embeddable, markdownContext, - onCancel, - onSave, startDate, endDate, lensTitle, + onCancel, + onSave, ]); const originatingPath = useMemo(() => `${location.pathname}${location.search}`, [ @@ -168,14 +172,14 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ { id: '', timeRange: { - from: (lensEmbeddableAttributes && startDate) ?? 'now-5d', + from: (lensEmbeddableAttributes && startDate) ?? 'now-7d', to: (lensEmbeddableAttributes && endDate) ?? 'now', mode: lensEmbeddableAttributes ? 'absolute' : 'relative', }, attributes: lensEmbeddableAttributes ?? getLensAttributes(await indexPatterns.getDefault()), }, { - originatingApp: 'securitySolution:case', + originatingApp: currentAppId!, originatingPath, } ); @@ -190,12 +194,12 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ startDate, endDate, indexPatterns, + currentAppId, originatingPath, ]); const handleChooseLensSO = useCallback( (savedObjectId, savedObjectType, fullName, savedObject) => { - console.error('apya', savedObjectId, savedObjectType, fullName, savedObject); setLensEmbeddableAttributes({ ...savedObject.attributes, title: '', @@ -209,9 +213,21 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ const handleCloseLensSOModal = useCallback(() => setShowLensSavedObjectsModal(false), []); useEffect(() => { - const incomingEmbeddablePackage = embeddable - .getStateTransfer() - .getIncomingEmbeddablePackage('securitySolution:case', true) as LensIncomingEmbeddablePackage; + const getCurrentAppId = async () => { + const appId = await currentAppId$.pipe(first()).toPromise(); + setCurrentAppId(appId); + }; + getCurrentAppId(); + }, [currentAppId$]); + + useEffect(() => { + let incomingEmbeddablePackage; + + if (currentAppId) { + incomingEmbeddablePackage = embeddable + ?.getStateTransfer() + .getIncomingEmbeddablePackage(currentAppId) as LensIncomingEmbeddablePackage; + } if ( incomingEmbeddablePackage?.type === 'lens' && @@ -236,21 +252,7 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ // eslint-disable-next-line no-empty } catch (e) {} } - - // console.error('stoargesgeet', storage.get('xpack.cases.commentDraft')); - }, [embeddable, storage, timefilter]); - - useEffect(() => { - const getCurrentAppId = async () => { - const appId = await currentAppId$.pipe(first()).toPromise(); - currentAppId.current = appId; - }; - getCurrentAppId(); - }, [currentAppId$]); - - // console.error('markdownContext', markdownContext); - - // console.error('lensEmbeddableAttributes', lensEmbeddableAttributes); + }, [embeddable, storage, timefilter, currentAppId]); return ( <> diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/processor.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/processor.tsx index ce210964576c8..f3fa789df3543 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/processor.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/processor.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import React, { useCallback } from 'react'; +import { first } from 'rxjs/operators'; +import React, { useCallback, useState } from 'react'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiText, EuiSpacer } from '@elastic/eui'; import styled from 'styled-components'; import { useLocation } from 'react-router-dom'; @@ -44,10 +45,10 @@ const LensMarkDownRendererComponent: React.FC = ({ }) => { const location = useLocation(); const { - EmbeddableComponent, - navigateToPrefilledEditor, - canUseEditor, - } = useKibana().services.lens; + application: { currentAppId$ }, + lens: { EmbeddableComponent, navigateToPrefilledEditor, canUseEditor }, + } = useKibana().services; + const [currentAppId, setCurrentAppId] = useState(undefined); const handleClick = useCallback(() => { const options = viewMode @@ -55,7 +56,7 @@ const LensMarkDownRendererComponent: React.FC = ({ openInNewTab: true, } : { - originatingApp: 'securitySolution:case', + originatingApp: currentAppId, originatingPath: `${location.pathname}${location.search}`, }; @@ -75,6 +76,7 @@ const LensMarkDownRendererComponent: React.FC = ({ } }, [ attributes, + currentAppId, endDate, location.pathname, location.search, @@ -83,6 +85,14 @@ const LensMarkDownRendererComponent: React.FC = ({ viewMode, ]); + useEffect(() => { + const getCurrentAppId = async () => { + const appId = await currentAppId$.pipe(first()).toPromise(); + setCurrentAppId(appId); + }; + getCurrentAppId(); + }, [currentAppId$]); + return ( {attributes ? ( From 5034a24cba1ed0069f5aea6cc5aff5ab0d344709 Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Mon, 28 Jun 2021 01:45:39 +0200 Subject: [PATCH 15/41] lens in obs --- .../cases/public/components/create/index.tsx | 78 ++-- .../components/user_action_tree/index.tsx | 56 ++- x-pack/plugins/observability/kibana.json | 3 +- .../components/app/cases/case_view/index.tsx | 9 + .../markdown_editor/plugins/lens/constants.ts | 10 + .../markdown_editor/plugins/lens/context.tsx | 13 + .../markdown_editor/plugins/lens/helpers.ts | 90 +++++ .../markdown_editor/plugins/lens/index.ts | 18 + .../plugins/lens/lens_saved_objects_modal.tsx | 84 +++++ .../plugins/lens/modal_container.tsx | 16 + .../markdown_editor/plugins/lens/parser.ts | 76 ++++ .../markdown_editor/plugins/lens/plugin.tsx | 349 ++++++++++++++++++ .../plugins/lens/processor.tsx | 142 +++++++ .../plugins/lens/translations.ts | 55 +++ .../exploratory_view/header/header.test.tsx | 4 +- x-pack/plugins/observability/public/plugin.ts | 2 + .../components/markdown_editor/editor.tsx | 2 - .../plugins/lens/processor.tsx | 2 +- 18 files changed, 930 insertions(+), 79 deletions(-) create mode 100644 x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/constants.ts create mode 100644 x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/context.tsx create mode 100644 x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/helpers.ts create mode 100644 x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/index.ts create mode 100644 x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx create mode 100644 x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/modal_container.tsx create mode 100644 x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/parser.ts create mode 100644 x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/plugin.tsx create mode 100644 x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/processor.tsx create mode 100644 x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/translations.ts diff --git a/x-pack/plugins/cases/public/components/create/index.tsx b/x-pack/plugins/cases/public/components/create/index.tsx index 5f6c832a22a30..cc2fac856092a 100644 --- a/x-pack/plugins/cases/public/components/create/index.tsx +++ b/x-pack/plugins/cases/public/components/create/index.tsx @@ -17,7 +17,7 @@ import { SubmitCaseButton } from './submit_button'; import { Case } from '../../containers/types'; import { CaseType } from '../../../common'; import { CasesTimelineIntegration, CasesTimelineIntegrationProvider } from '../timeline_context'; -import { CasesLensIntegration, CasesLensIntegrationProvider } from '../lens_context'; +// import { CasesLensIntegration, CasesLensIntegrationProvider } from '../lens_context'; import { fieldName as descriptionFieldName } from './description'; import { InsertTimeline } from '../insert_timeline'; import { UsePostComment } from '../../containers/use_post_comment'; @@ -39,7 +39,7 @@ export interface CreateCaseProps extends Owner { hideConnectorServiceNowSir?: boolean; onCancel: () => void; onSuccess: (theCase: Case) => Promise; - lensIntegration?: CasesLensIntegration; + // lensIntegration?: CasesLensIntegration; timelineIntegration?: CasesTimelineIntegration; withSteps?: boolean; } @@ -51,48 +51,48 @@ const CreateCaseComponent = ({ disableAlerts, onCancel, onSuccess, - lensIntegration, + // lensIntegration, timelineIntegration, withSteps, }: Omit) => ( - - */} + + - - - - - - {i18n.CANCEL} - - - - - - - - - - + disableAlerts={disableAlerts} + withSteps={withSteps} + /> + + + + + {i18n.CANCEL} + + + + + + + + + + {/* */} ); diff --git a/x-pack/plugins/cases/public/components/user_action_tree/index.tsx b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx index a5972a29511ff..cf60d6d99846b 100644 --- a/x-pack/plugins/cases/public/components/user_action_tree/index.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx @@ -151,7 +151,6 @@ export const UserActionTree = React.memo( const [selectedOutlineCommentId, setSelectedOutlineCommentId] = useState(''); const { isLoadingIds, patchComment } = useUpdateComment(); const currentUser = useCurrentUser(); - const [currentAppId, setCurrentAppId] = useState(null); const [manageMarkdownEditIds, setManageMarkdownEditIds] = useState([]); const [loadingAlertData, manualAlertsData] = useFetchAlertData( @@ -603,45 +602,31 @@ export const UserActionTree = React.memo( const comments = [...userActions, ...bottomActions]; useEffect(() => { - const getCurrentAppId = async () => { - const appId = await currentAppId$.pipe(first()).toPromise(); - setCurrentAppId(appId); - }; - getCurrentAppId(); - }, [currentAppId$]); + const setInitialLensComment = async () => { + const currentAppId = await currentAppId$.pipe(first()).toPromise(); - useEffect(() => { - let incomingEmbeddablePackage; + if (!currentAppId) { + return; + } - if (currentAppId) { - incomingEmbeddablePackage = embeddable + const incomingEmbeddablePackage = embeddable ?.getStateTransfer() .getIncomingEmbeddablePackage(currentAppId); - } - - if (incomingEmbeddablePackage) { - let draftComment; - if (storage.get('xpack.cases.commentDraft')) { - try { - draftComment = JSON.parse(storage.get('xpack.cases.commentDraft')); - // eslint-disable-next-line no-empty - } catch (e) {} - } - if (draftComment) { - if (!draftComment.commentId) { - if (commentRefs.current && commentRefs.current[NEW_ID]) { - commentRefs.current[NEW_ID].setComment(draftComment.comment); - const buttons = commentRefs.current[NEW_ID].editor.toolbar?.querySelector( - '[aria-label="Insert lens link"]' - ); - buttons?.click(); - return; - } + if (incomingEmbeddablePackage) { + let draftComment; + if (storage.get('xpack.cases.commentDraft')) { + try { + draftComment = JSON.parse(storage.get('xpack.cases.commentDraft')); + // eslint-disable-next-line no-empty + } catch (e) {} } - if (draftComment.commentId) { - if (!manageMarkdownEditIds.includes(draftComment.commentId)) { + if (draftComment?.commentId) { + if ( + ![NEW_ID, DESCRIPTION_ID].includes(draftComment.commentId) && + !manageMarkdownEditIds.includes(draftComment.commentId) + ) { setManageMarkdownEditIds([draftComment.commentId]); } @@ -661,8 +646,9 @@ export const UserActionTree = React.memo( } } } - } - }, [currentAppId, embeddable, manageMarkdownEditIds, storage]); + }; + setInitialLensComment(); + }, [currentAppId$, embeddable, manageMarkdownEditIds, storage]); return ( <> diff --git a/x-pack/plugins/observability/kibana.json b/x-pack/plugins/observability/kibana.json index 29975a7ad05a4..8329ead209898 100644 --- a/x-pack/plugins/observability/kibana.json +++ b/x-pack/plugins/observability/kibana.json @@ -26,6 +26,7 @@ "requiredBundles": [ "data", "kibanaReact", - "kibanaUtils" + "kibanaUtils", + "savedObjects" ] } diff --git a/x-pack/plugins/observability/public/components/app/cases/case_view/index.tsx b/x-pack/plugins/observability/public/components/app/cases/case_view/index.tsx index 07d8019153a06..bbdc924b8ea97 100644 --- a/x-pack/plugins/observability/public/components/app/cases/case_view/index.tsx +++ b/x-pack/plugins/observability/public/components/app/cases/case_view/index.tsx @@ -19,6 +19,7 @@ import { useFetchAlertData } from './helpers'; import { useKibana } from '../../../../utils/kibana_react'; import { CASES_APP_ID } from '../constants'; import { useBreadcrumbs } from '../../../../hooks/use_breadcrumbs'; +import * as lensMarkdownPlugin from './markdown_editor/plugins/lens'; interface Props { caseId: string; @@ -116,5 +117,13 @@ export const CaseView = React.memo(({ caseId, subCaseId, userCanCrud }: Props) = subCaseId, useFetchAlertData, userCanCrud, + lensIntegration: { + editor_context: lensMarkdownPlugin.context, + editor_plugins: { + parsingPlugin: lensMarkdownPlugin.parser, + processingPluginRenderer: lensMarkdownPlugin.renderer, + uiPlugin: lensMarkdownPlugin.plugin, + }, + }, }); }); diff --git a/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/constants.ts b/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/constants.ts new file mode 100644 index 0000000000000..a88051d8e4542 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/constants.ts @@ -0,0 +1,10 @@ +/* + * 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. + */ + +export const ID = 'lens'; +export const PREFIX = `[`; +export const LENS_VISUALIZATION_HEIGHT = 200; diff --git a/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/context.tsx b/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/context.tsx new file mode 100644 index 0000000000000..d7f5b0612cb73 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/context.tsx @@ -0,0 +1,13 @@ +/* + * 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. + */ + +import React from 'react'; + +export const CommentEditorContext = React.createContext<{ + editorId: string; + value: string; +} | null>(null); diff --git a/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/helpers.ts b/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/helpers.ts new file mode 100644 index 0000000000000..acd1cc3a2725c --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/helpers.ts @@ -0,0 +1,90 @@ +/* + * 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. + */ + +import type { IndexPattern } from 'src/plugins/data/public'; +import type { + TypedLensByValueInput, + PersistedIndexPatternLayer, + XYState, +} from '../../../../../../../../../lens/public'; + +// Generate a Lens state based on some app-specific input parameters. +// `TypedLensByValueInput` can be used for type-safety - it uses the same interfaces as Lens-internal code. +export const getLensAttributes = ( + defaultIndexPattern: IndexPattern | null +): TypedLensByValueInput['attributes'] => { + const dataLayer: PersistedIndexPatternLayer = { + columnOrder: ['col1', 'col2'], + columns: { + col2: { + dataType: 'number', + isBucketed: false, + label: 'Count of records', + operationType: 'count', + scale: 'ratio', + sourceField: 'Records', + }, + col1: { + dataType: 'date', + isBucketed: true, + label: '@timestamp', + operationType: 'date_histogram', + params: { interval: 'auto' }, + scale: 'interval', + sourceField: defaultIndexPattern?.timeFieldName!, + }, + }, + }; + + const xyConfig: XYState = { + axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true }, + fittingFunction: 'None', + gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true }, + layers: [ + { + accessors: ['col2'], + layerId: 'layer1', + seriesType: 'bar_stacked', + xAccessor: 'col1', + yConfig: [{ forAccessor: 'col2', color: undefined }], + }, + ], + legend: { isVisible: true, position: 'right' }, + preferredSeriesType: 'bar_stacked', + tickLabelsVisibilitySettings: { x: true, yLeft: true, yRight: true }, + valueLabels: 'hide', + }; + + return { + visualizationType: 'lnsXY', + title: '', + references: [ + { + id: defaultIndexPattern?.id!, + name: 'indexpattern-datasource-current-indexpattern', + type: 'index-pattern', + }, + { + id: defaultIndexPattern?.id!, + name: 'indexpattern-datasource-layer-layer1', + type: 'index-pattern', + }, + ], + state: { + datasourceStates: { + indexpattern: { + layers: { + layer1: dataLayer, + }, + }, + }, + filters: [], + query: { language: 'kuery', query: '' }, + visualization: xyConfig, + }, + }; +}; diff --git a/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/index.ts b/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/index.ts new file mode 100644 index 0000000000000..b0320954c97f0 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/index.ts @@ -0,0 +1,18 @@ +/* + * 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. + */ + +import { plugin } from './plugin'; +import { LensParser } from './parser'; +import { LensMarkDownRenderer } from './processor'; +import { CommentEditorContext } from './context'; + +export { + CommentEditorContext as context, + plugin, + LensParser as parser, + LensMarkDownRenderer as renderer, +}; diff --git a/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx b/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx new file mode 100644 index 0000000000000..75c6c28401735 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx @@ -0,0 +1,84 @@ +/* + * 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. + */ + +import { + EuiModal, + EuiModalProps, + EuiModalBody, + EuiModalHeader, + EuiModalHeaderTitle, +} from '@elastic/eui'; +import React from 'react'; + +import { + SavedObjectFinderUi, + SavedObjectFinderUiProps, +} from '../../../../../../../../../../../src/plugins/saved_objects/public'; +import { useKibana } from '../../../../../../../utils/kibana_react'; +import { ModalContainer } from './modal_container'; + +interface LensSavedObjectsModalProps { + onClose: EuiModalProps['onClose']; + onChoose: SavedObjectFinderUiProps['onChoose']; +} + +// eslint-disable-next-line react/function-component-definition +const LensSavedObjectsModalComponent: React.FC = ({ + onClose, + onChoose, +}) => { + const { savedObjects, uiSettings } = useKibana().services; + + return ( + + + + +

{'Modal title'}

+
+
+ + + 'lensApp', + name: 'Lens', + includeFields: ['*'], + // i18n.translate( + // 'xpack.transform.newTransform.searchSelection.savedObjectType.search', + // { + // defaultMessage: 'Lens', + // } + // ), + }, + ]} + fixedPageSize={10} + uiSettings={uiSettings} + savedObjects={savedObjects} + /> + +
+
+ ); +}; + +export const LensSavedObjectsModal = React.memo(LensSavedObjectsModalComponent); diff --git a/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/modal_container.tsx b/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/modal_container.tsx new file mode 100644 index 0000000000000..0f70e80deed41 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/modal_container.tsx @@ -0,0 +1,16 @@ +/* + * 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. + */ + +import styled from 'styled-components'; + +export const ModalContainer = styled.div` + width: ${({ theme }) => theme.eui.euiBreakpoints.m}; + + .euiModalBody { + min-height: 300px; + } +`; diff --git a/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/parser.ts b/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/parser.ts new file mode 100644 index 0000000000000..5d4ec22b93e2b --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/parser.ts @@ -0,0 +1,76 @@ +/* + * 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. + */ + +import { Plugin } from 'unified'; +import { RemarkTokenizer } from '@elastic/eui'; + +export const LensParser: Plugin = function () { + const Parser = this.Parser; + const tokenizers = Parser.prototype.blockTokenizers; + const methods = Parser.prototype.blockMethods; + + const tokenizeLens: RemarkTokenizer = function (eat, value, silent) { + if (value.startsWith('!{lens') === false) return false; + + const nextChar = value[6]; + + if (nextChar !== '{' && nextChar !== '}') return false; // this isn't actually a lens + + if (silent) { + return true; + } + + // is there a configuration? + const hasConfiguration = nextChar === '{'; + + let match = '!{lens'; + let configuration = {}; + + if (hasConfiguration) { + let configurationString = ''; + + let openObjects = 0; + + for (let i = 6; i < value.length; i++) { + const char = value[i]; + if (char === '{') { + openObjects++; + configurationString += char; + } else if (char === '}') { + openObjects--; + if (openObjects === -1) { + break; + } + configurationString += char; + } else { + configurationString += char; + } + } + + match += configurationString; + try { + configuration = JSON.parse(configurationString); + } catch (e) { + const now = eat.now(); + this.file.fail(`Unable to parse lens JSON configuration: ${e}`, { + line: now.line, + column: now.column + 6, + }); + } + } + + match += '}'; + + return eat(match)({ + type: 'lens', + ...configuration, + }); + }; + + tokenizers.lens = tokenizeLens; + methods.splice(methods.indexOf('text'), 0, 'lens'); +}; diff --git a/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/plugin.tsx b/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/plugin.tsx new file mode 100644 index 0000000000000..a126f22e42f87 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/plugin.tsx @@ -0,0 +1,349 @@ +/* + * 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. + */ + +import { first } from 'rxjs/operators'; +import { + EuiFieldText, + EuiModalBody, + EuiModalHeader, + EuiModalHeaderTitle, + EuiMarkdownEditorUiPlugin, + EuiMarkdownContext, + EuiCodeBlock, + EuiSpacer, + EuiModalFooter, + EuiButtonEmpty, + EuiButton, + EuiFlexItem, + EuiFlexGroup, + EuiFormRow, + EuiMarkdownAstNodePosition, +} from '@elastic/eui'; +import React, { useCallback, useContext, useMemo, useEffect, useState } from 'react'; +import moment from 'moment'; +import { useLocation } from 'react-router-dom'; + +import type { TypedLensByValueInput } from '../../../../../../../../../lens/public'; +import { useKibana } from '../../../../../../../utils/kibana_react'; +import { LensMarkDownRenderer } from './processor'; +import { ID } from './constants'; +import * as i18n from './translations'; +import { CommentEditorContext } from './context'; +import { LensSavedObjectsModal } from './lens_saved_objects_modal'; +import { ModalContainer } from './modal_container'; +import { getLensAttributes } from './helpers'; +import type { + EmbeddablePackageState, + EmbeddableInput, +} from '../../../../../../../../../../../src/plugins/embeddable/public'; + +type LensIncomingEmbeddablePackage = Omit & { + input: Omit & { + id: string | undefined; + attributes: TypedLensByValueInput['attributes']; + }; +}; + +type LensEuiMarkdownEditorUiPlugin = EuiMarkdownEditorUiPlugin<{ + title: string; + startDate: string; + endDate: string; + position: EuiMarkdownAstNodePosition; + attributes: TypedLensByValueInput['attributes']; +}>; + +// eslint-disable-next-line react/function-component-definition +const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ + node, + onCancel, + onSave, +}) => { + const location = useLocation(); + const { + application: { currentAppId$ }, + embeddable, + lens, + storage, + data: { + indexPatterns, + query: { + timefilter: { timefilter }, + }, + }, + } = useKibana().services; + + const [currentAppId, setCurrentAppId] = useState(undefined); + + const [editMode, setEditMode] = useState(!!node); + const [lensEmbeddableAttributes, setLensEmbeddableAttributes] = useState( + node?.attributes ?? null + ); + const [startDate, setStartDate] = useState( + node?.startDate ? moment(node.startDate).format() : 'now-7d' + ); + const [endDate, setEndDate] = useState( + node?.endDate ? moment(node.endDate).format() : 'now' + ); + const [lensTitle, setLensTitle] = useState(node?.title ?? ''); + const [showLensSavedObjectsModal, setShowLensSavedObjectsModal] = useState(false); + const commentEditorContext = useContext(CommentEditorContext); + const markdownContext = useContext(EuiMarkdownContext); + + const handleTitleChange = useCallback((e) => { + setLensTitle(e.target.value); + }, []); + + const handleAdd = useCallback(() => { + let draftComment; + if (storage.get('xpack.cases.commentDraft')) { + try { + draftComment = JSON.parse(storage.get('xpack.cases.commentDraft')); + // eslint-disable-next-line no-empty + } catch (e) {} + } + + if (currentAppId) { + // clears Lens incoming state + embeddable?.getStateTransfer().getIncomingEmbeddablePackage(currentAppId, true); + } + + if (!node && draftComment.position) { + markdownContext.replaceNode( + draftComment.position, + `!{lens${JSON.stringify({ + startDate, + endDate, + title: lensTitle, + attributes: lensEmbeddableAttributes, + })}}` + ); + + onCancel(); + return; + } + + if (lensEmbeddableAttributes) { + onSave( + `!{lens${JSON.stringify({ + startDate, + endDate, + title: lensTitle, + attributes: lensEmbeddableAttributes, + })}}`, + { + block: true, + } + ); + } + }, [ + storage, + currentAppId, + node, + lensEmbeddableAttributes, + embeddable, + markdownContext, + startDate, + endDate, + lensTitle, + onCancel, + onSave, + ]); + + const originatingPath = useMemo(() => `${location.pathname}${location.search}`, [ + location.pathname, + location.search, + ]); + + const handleEditInLensClick = useCallback(async () => { + storage.set( + 'xpack.cases.commentDraft', + JSON.stringify({ + commentId: commentEditorContext?.editorId, + comment: commentEditorContext?.value, + position: node?.position, + title: lensTitle, + }) + ); + + lens?.navigateToPrefilledEditor( + { + id: '', + timeRange: { + from: (lensEmbeddableAttributes && startDate) ?? 'now-7d', + to: (lensEmbeddableAttributes && endDate) ?? 'now', + mode: lensEmbeddableAttributes ? 'absolute' : 'relative', + }, + attributes: lensEmbeddableAttributes ?? getLensAttributes(await indexPatterns.getDefault()), + }, + { + originatingApp: currentAppId!, + originatingPath, + } + ); + }, [ + storage, + commentEditorContext?.editorId, + commentEditorContext?.value, + node?.position, + lensTitle, + lens, + lensEmbeddableAttributes, + startDate, + endDate, + indexPatterns, + currentAppId, + originatingPath, + ]); + + const handleChooseLensSO = useCallback( + (savedObjectId, savedObjectType, fullName, savedObject) => { + setLensEmbeddableAttributes({ + ...savedObject.attributes, + title: '', + references: savedObject.references, + }); + setShowLensSavedObjectsModal(false); + }, + [] + ); + + const handleCloseLensSOModal = useCallback(() => setShowLensSavedObjectsModal(false), []); + + useEffect(() => { + const getCurrentAppId = async () => { + const appId = await currentAppId$.pipe(first()).toPromise(); + setCurrentAppId(appId); + }; + getCurrentAppId(); + }, [currentAppId$]); + + useEffect(() => { + let incomingEmbeddablePackage; + + if (currentAppId) { + incomingEmbeddablePackage = embeddable + ?.getStateTransfer() + .getIncomingEmbeddablePackage(currentAppId) as LensIncomingEmbeddablePackage; + } + + if ( + incomingEmbeddablePackage?.type === 'lens' && + incomingEmbeddablePackage?.input?.attributes + ) { + setLensEmbeddableAttributes(incomingEmbeddablePackage?.input.attributes); + const lensTime = timefilter.getTime(); + if (lensTime?.from && lensTime?.to) { + setStartDate(lensTime.from); + setEndDate(lensTime.to); + } + } + + let draftComment; + if (storage.get('xpack.cases.commentDraft')) { + try { + draftComment = JSON.parse(storage.get('xpack.cases.commentDraft')); + if (draftComment.title) { + setLensTitle(draftComment.title); + } + setEditMode(true); + // eslint-disable-next-line no-empty + } catch (e) {} + } + }, [embeddable, storage, timefilter, currentAppId]); + + return ( + <> + + + + {node ? 'Edit Lens visualization' : 'Add Lens visualization'} + + + + + + + {'Create visualization'} + + + + setShowLensSavedObjectsModal(true)} + > + {'Add from library'} + + + + {lensEmbeddableAttributes ? ( + + + + + + + + + {`Edit in Lens`} + + + + ) : null} + + + + + {'Cancel'} + + {editMode ? 'Update' : 'Add to a Case'} + + + + {showLensSavedObjectsModal ? ( + + ) : null} + + ); +}; + +export const LensEditor = React.memo(LensEditorComponent); + +export const plugin: LensEuiMarkdownEditorUiPlugin = { + name: ID, + button: { + label: i18n.INSERT_LENS, + iconType: 'lensApp', + }, + helpText: ( + + {'!{lens}'} + + ), + editor: LensEditor, +}; diff --git a/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/processor.tsx b/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/processor.tsx new file mode 100644 index 0000000000000..e54900ad30856 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/processor.tsx @@ -0,0 +1,142 @@ +/* + * 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. + */ + +import { first } from 'rxjs/operators'; +import React, { useCallback, useEffect, useState } from 'react'; +import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiText, EuiSpacer } from '@elastic/eui'; +import styled from 'styled-components'; +import { useLocation } from 'react-router-dom'; + +import { createGlobalStyle } from '../../../../../../../../../../../src/plugins/kibana_react/common'; +import { TypedLensByValueInput } from '../../../../../../../../../lens/public'; +import { useKibana } from '../../../../../../../utils/kibana_react'; +import { LENS_VISUALIZATION_HEIGHT } from './constants'; + +const Container = styled.div` + min-height: ${LENS_VISUALIZATION_HEIGHT}px; +`; + +// when displaying chart in modal the tooltip is render under the modal +const LensChartTooltipFix = createGlobalStyle` + div.euiOverlayMask.euiOverlayMask--aboveHeader ~ [id^='echTooltipPortal'] { + z-index: ${({ theme }) => theme.eui.euiZLevel7} !important; + } +`; + +interface LensMarkDownRendererProps { + attributes: TypedLensByValueInput['attributes'] | null; + id?: string | null; + title?: string | null; + startDate?: string | null; + endDate?: string | null; + viewMode?: boolean | undefined; +} + +// eslint-disable-next-line react/function-component-definition +const LensMarkDownRendererComponent: React.FC = ({ + attributes, + title, + startDate, + endDate, + viewMode = true, +}) => { + const location = useLocation(); + const { + application: { currentAppId$ }, + lens: { EmbeddableComponent, navigateToPrefilledEditor, canUseEditor }, + } = useKibana().services; + const [currentAppId, setCurrentAppId] = useState(undefined); + + const handleClick = useCallback(() => { + const options = viewMode + ? { + openInNewTab: true, + } + : { + originatingApp: currentAppId, + originatingPath: `${location.pathname}${location.search}`, + }; + + if (attributes) { + navigateToPrefilledEditor( + { + id: '', + timeRange: { + from: startDate ?? 'now-7d', + to: endDate ?? 'now', + mode: startDate ? 'absolute' : 'relative', + }, + attributes, + }, + options + ); + } + }, [ + attributes, + currentAppId, + endDate, + location.pathname, + location.search, + navigateToPrefilledEditor, + startDate, + viewMode, + ]); + + useEffect(() => { + const getCurrentAppId = async () => { + const appId = await currentAppId$.pipe(first()).toPromise(); + setCurrentAppId(appId); + }; + getCurrentAppId(); + }, [currentAppId$]); + + return ( + + {attributes ? ( + <> + + + +
{title}
+
+
+ + {viewMode ? ( + + {`Open in Lens`} + + ) : null} + +
+ + + + {attributes ? ( + + ) : null} + + + ) : null} +
+ ); +}; + +export const LensMarkDownRenderer = React.memo(LensMarkDownRendererComponent); diff --git a/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/translations.ts b/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/translations.ts new file mode 100644 index 0000000000000..476a4bfdfe0bc --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/translations.ts @@ -0,0 +1,55 @@ +/* + * 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. + */ + +import { i18n } from '@kbn/i18n'; + +export const INSERT_LENS = i18n.translate( + 'xpack.securitySolution.markdownEditor.plugins.lens.insertLensButtonLabel', + { + defaultMessage: 'Insert lens link', + } +); + +export const LENS_ID = (lensId: string) => + i18n.translate('xpack.securitySolution.markdownEditor.plugins.lens.toolTip.lensId', { + defaultMessage: 'Lens id: { lensId }', + values: { + lensId, + }, + }); + +export const NO_LENS_NAME_FOUND = i18n.translate( + 'xpack.securitySolution.markdownEditor.plugins.lens.noLensNameFoundErrorMsg', + { + defaultMessage: 'No lens name found', + } +); + +export const NO_LENS_ID_FOUND = i18n.translate( + 'xpack.securitySolution.markdownEditor.plugins.lens.noLensIdFoundErrorMsg', + { + defaultMessage: 'No lens id found', + } +); + +export const LENS_URL_IS_NOT_VALID = (lensUrl: string) => + i18n.translate( + 'xpack.securitySolution.markdownEditor.plugins.lens.toolTip.lensUrlIsNotValidErrorMsg', + { + defaultMessage: 'Lens URL is not valid => {lensUrl}', + values: { + lensUrl, + }, + } + ); + +export const NO_PARENTHESES = i18n.translate( + 'xpack.securitySolution.markdownEditor.plugins.lens.noParenthesesErrorMsg', + { + defaultMessage: 'Expected left parentheses', + } +); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.test.tsx index 8cd8977fcf741..fe44a55d56851 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.test.tsx @@ -52,7 +52,9 @@ describe('ExploratoryViewHeader', function () { to: 'now', }, }, - true + { + openInNewTab: true, + } ); }); }); diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts index 03c3fb3c27e58..592be5d1448bc 100644 --- a/x-pack/plugins/observability/public/plugin.ts +++ b/x-pack/plugins/observability/public/plugin.ts @@ -24,6 +24,7 @@ import type { DataPublicPluginSetup, DataPublicPluginStart, } from '../../../../src/plugins/data/public'; +import type { EmbeddableStart } from '../../../../src/plugins/embeddable/public'; import type { HomePublicPluginSetup, HomePublicPluginStart, @@ -49,6 +50,7 @@ export interface ObservabilityPublicPluginsSetup { export interface ObservabilityPublicPluginsStart { cases: CasesUiStart; + embeddable: EmbeddableStart; home?: HomePublicPluginStart; triggersActionsUi: TriggersAndActionsUIPublicPluginStart; data: DataPublicPluginStart; diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/editor.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/editor.tsx index ad36042030ae4..b01b227ed9e6d 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/editor.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/editor.tsx @@ -49,8 +49,6 @@ const MarkdownEditorComponent = forwardRef { - // console.error('reft2222', ref, editorRef); - if (!editorRef.current) { return null; } diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/processor.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/processor.tsx index f3fa789df3543..c7f4241f9dd1f 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/processor.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/processor.tsx @@ -6,7 +6,7 @@ */ import { first } from 'rxjs/operators'; -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiText, EuiSpacer } from '@elastic/eui'; import styled from 'styled-components'; import { useLocation } from 'react-router-dom'; From a05939d61d18885c021f874bdbc4c365113f65a3 Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Mon, 28 Jun 2021 01:52:45 +0200 Subject: [PATCH 16/41] update types --- .../public/cases/components/create/index.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/security_solution/public/cases/components/create/index.tsx b/x-pack/plugins/security_solution/public/cases/components/create/index.tsx index 41e94d283ea6e..726228e0f4751 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/create/index.tsx @@ -11,7 +11,7 @@ import { EuiPanel } from '@elastic/eui'; import { getCaseDetailsUrl, getCaseUrl } from '../../../common/components/link_to'; import { useKibana } from '../../../common/lib/kibana'; import * as timelineMarkdownPlugin from '../../../common/components/markdown_editor/plugins/timeline'; -import * as lensMarkdownPlugin from '../../../common/components/markdown_editor/plugins/lens'; +// import * as lensMarkdownPlugin from '../../../common/components/markdown_editor/plugins/lens'; import { useInsertTimeline } from '../use_insert_timeline'; import { APP_ID, CASES_APP_ID } from '../../../../common/constants'; import { useGetUrlSearch } from '../../../common/components/navigation/use_get_url_search'; @@ -43,14 +43,14 @@ export const Create = React.memo(() => { {cases.getCreateCase({ onCancel: handleSetIsCancel, onSuccess, - lensIntegration: { - editor_context: lensMarkdownPlugin.context, - editor_plugins: { - parsingPlugin: lensMarkdownPlugin.parser, - processingPluginRenderer: lensMarkdownPlugin.renderer, - uiPlugin: lensMarkdownPlugin.plugin, - }, - }, + // lensIntegration: { + // editor_context: lensMarkdownPlugin.context, + // editor_plugins: { + // parsingPlugin: lensMarkdownPlugin.parser, + // processingPluginRenderer: lensMarkdownPlugin.renderer, + // uiPlugin: lensMarkdownPlugin.plugin, + // }, + // }, timelineIntegration: { editor_plugins: { parsingPlugin: timelineMarkdownPlugin.parser, From 4a62f3d1689789c742654c89890b027067caec3d Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Mon, 28 Jun 2021 12:54:42 +0200 Subject: [PATCH 17/41] i18n --- .../components/user_action_tree/constants.ts | 8 +++ .../components/user_action_tree/index.tsx | 7 +- .../user_action_tree/translations.ts | 7 ++ .../markdown_editor/plugins/lens/constants.ts | 1 + .../plugins/lens/lens_saved_objects_modal.tsx | 55 +++++++------- .../markdown_editor/plugins/lens/parser.ts | 9 +-- .../markdown_editor/plugins/lens/plugin.tsx | 69 +++++++++++++----- .../plugins/lens/translations.ts | 55 -------------- .../markdown_editor/plugins/lens/constants.ts | 1 + .../plugins/lens/lens_saved_objects_modal.tsx | 56 ++++++++------- .../markdown_editor/plugins/lens/plugin.tsx | 72 ++++++++++++++----- .../plugins/lens/translations.ts | 55 -------------- 12 files changed, 198 insertions(+), 197 deletions(-) create mode 100644 x-pack/plugins/cases/public/components/user_action_tree/constants.ts delete mode 100644 x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/translations.ts delete mode 100644 x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/translations.ts diff --git a/x-pack/plugins/cases/public/components/user_action_tree/constants.ts b/x-pack/plugins/cases/public/components/user_action_tree/constants.ts new file mode 100644 index 0000000000000..584194be65f50 --- /dev/null +++ b/x-pack/plugins/cases/public/components/user_action_tree/constants.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export const DRAFT_COMMENT_STORAGE_ID = 'xpack.cases.commentDraft'; diff --git a/x-pack/plugins/cases/public/components/user_action_tree/index.tsx b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx index cf60d6d99846b..2ca0a5fffa30a 100644 --- a/x-pack/plugins/cases/public/components/user_action_tree/index.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx @@ -53,6 +53,7 @@ import { UserActionTimestamp } from './user_action_timestamp'; import { UserActionUsername } from './user_action_username'; import { UserActionContentToolbar } from './user_action_content_toolbar'; import { getManualAlertIdsWithNoRuleId } from '../case_view/helpers'; +import { DRAFT_COMMENT_STORAGE_ID } from './constants'; export interface UserActionTreeProps { caseServices: CaseServices; @@ -615,9 +616,9 @@ export const UserActionTree = React.memo( if (incomingEmbeddablePackage) { let draftComment; - if (storage.get('xpack.cases.commentDraft')) { + if (storage.get(DRAFT_COMMENT_STORAGE_ID)) { try { - draftComment = JSON.parse(storage.get('xpack.cases.commentDraft')); + draftComment = JSON.parse(storage.get(DRAFT_COMMENT_STORAGE_ID)); // eslint-disable-next-line no-empty } catch (e) {} } @@ -639,7 +640,7 @@ export const UserActionTree = React.memo( commentRefs.current[draftComment.commentId].setComment(draftComment.comment); const lensPluginButton = commentRefs.current[ draftComment.commentId - ].editor?.toolbar?.querySelector('[aria-label="Insert lens link"]'); + ].editor?.toolbar?.querySelector(`[aria-label="${i18n.INSERT_LENS}"]`); if (lensPluginButton) { lensPluginButton.click(); } diff --git a/x-pack/plugins/cases/public/components/user_action_tree/translations.ts b/x-pack/plugins/cases/public/components/user_action_tree/translations.ts index 27d1554ed255b..f9c1a878f9829 100644 --- a/x-pack/plugins/cases/public/components/user_action_tree/translations.ts +++ b/x-pack/plugins/cases/public/components/user_action_tree/translations.ts @@ -56,3 +56,10 @@ export const SHOW_ALERT_TOOLTIP = i18n.translate('xpack.cases.caseView.showAlert export const UNKNOWN_RULE = i18n.translate('xpack.cases.caseView.unknownRule.label', { defaultMessage: 'Unknown rule', }); + +export const INSERT_LENS = i18n.translate( + 'xpack.cases.markdownEditor.plugins.lens.insertLensButtonLabel', + { + defaultMessage: 'Insert lens visualization', + } +); diff --git a/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/constants.ts b/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/constants.ts index a88051d8e4542..05826f73fe007 100644 --- a/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/constants.ts +++ b/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/constants.ts @@ -8,3 +8,4 @@ export const ID = 'lens'; export const PREFIX = `[`; export const LENS_VISUALIZATION_HEIGHT = 200; +export const DRAFT_COMMENT_STORAGE_ID = 'xpack.cases.commentDraft'; diff --git a/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx b/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx index 75c6c28401735..19da69327b0c9 100644 --- a/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx +++ b/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx @@ -12,7 +12,9 @@ import { EuiModalHeader, EuiModalHeaderTitle, } from '@elastic/eui'; -import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import React, { useMemo } from 'react'; import { SavedObjectFinderUi, @@ -33,12 +35,34 @@ const LensSavedObjectsModalComponent: React.FC = ({ }) => { const { savedObjects, uiSettings } = useKibana().services; + const savedObjectMetaData = useMemo( + () => [ + { + type: 'lens', + getIconForSavedObject: () => 'lensApp', + name: i18n.translate( + 'xpack.observability.markdownEditor.plugins.lens.insertLensSavedObjectModal.searchSelection.savedObjectType.lens', + { + defaultMessage: 'Lens', + } + ), + includeFields: ['*'], + }, + ], + [] + ); + return ( -

{'Modal title'}

+

+ +

@@ -48,29 +72,12 @@ const LensSavedObjectsModalComponent: React.FC = ({ onChoose={onChoose} showFilter noItemsMessage={ - 'No matching lens found.' - - // i18n.translate( - // 'xpack.transform.newTransform.searchSelection.notFoundLabel', - // { - // defaultMessage: 'No matching lens found.', - // } - // ) + } - savedObjectMetaData={[ - { - type: 'lens', - getIconForSavedObject: () => 'lensApp', - name: 'Lens', - includeFields: ['*'], - // i18n.translate( - // 'xpack.transform.newTransform.searchSelection.savedObjectType.search', - // { - // defaultMessage: 'Lens', - // } - // ), - }, - ]} + savedObjectMetaData={savedObjectMetaData} fixedPageSize={10} uiSettings={uiSettings} savedObjects={savedObjects} diff --git a/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/parser.ts b/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/parser.ts index 5d4ec22b93e2b..8d598fad260dc 100644 --- a/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/parser.ts +++ b/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/parser.ts @@ -7,6 +7,7 @@ import { Plugin } from 'unified'; import { RemarkTokenizer } from '@elastic/eui'; +import { ID } from './constants'; export const LensParser: Plugin = function () { const Parser = this.Parser; @@ -14,7 +15,7 @@ export const LensParser: Plugin = function () { const methods = Parser.prototype.blockMethods; const tokenizeLens: RemarkTokenizer = function (eat, value, silent) { - if (value.startsWith('!{lens') === false) return false; + if (value.startsWith(`!{${ID}`) === false) return false; const nextChar = value[6]; @@ -27,7 +28,7 @@ export const LensParser: Plugin = function () { // is there a configuration? const hasConfiguration = nextChar === '{'; - let match = '!{lens'; + let match = `!{${ID}`; let configuration = {}; if (hasConfiguration) { @@ -66,11 +67,11 @@ export const LensParser: Plugin = function () { match += '}'; return eat(match)({ - type: 'lens', + type: ID, ...configuration, }); }; tokenizers.lens = tokenizeLens; - methods.splice(methods.indexOf('text'), 0, 'lens'); + methods.splice(methods.indexOf('text'), 0, ID); }; diff --git a/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/plugin.tsx b/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/plugin.tsx index a126f22e42f87..983fe58797a0f 100644 --- a/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/plugin.tsx +++ b/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/plugin.tsx @@ -24,14 +24,15 @@ import { EuiMarkdownAstNodePosition, } from '@elastic/eui'; import React, { useCallback, useContext, useMemo, useEffect, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; import moment from 'moment'; import { useLocation } from 'react-router-dom'; import type { TypedLensByValueInput } from '../../../../../../../../../lens/public'; import { useKibana } from '../../../../../../../utils/kibana_react'; import { LensMarkDownRenderer } from './processor'; -import { ID } from './constants'; -import * as i18n from './translations'; +import { DRAFT_COMMENT_STORAGE_ID, ID } from './constants'; import { CommentEditorContext } from './context'; import { LensSavedObjectsModal } from './lens_saved_objects_modal'; import { ModalContainer } from './modal_container'; @@ -99,9 +100,9 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ const handleAdd = useCallback(() => { let draftComment; - if (storage.get('xpack.cases.commentDraft')) { + if (storage.get(DRAFT_COMMENT_STORAGE_ID)) { try { - draftComment = JSON.parse(storage.get('xpack.cases.commentDraft')); + draftComment = JSON.parse(storage.get(DRAFT_COMMENT_STORAGE_ID)); // eslint-disable-next-line no-empty } catch (e) {} } @@ -114,7 +115,7 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ if (!node && draftComment.position) { markdownContext.replaceNode( draftComment.position, - `!{lens${JSON.stringify({ + `!{${ID}${JSON.stringify({ startDate, endDate, title: lensTitle, @@ -128,7 +129,7 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ if (lensEmbeddableAttributes) { onSave( - `!{lens${JSON.stringify({ + `!{${ID}${JSON.stringify({ startDate, endDate, title: lensTitle, @@ -160,7 +161,7 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ const handleEditInLensClick = useCallback(async () => { storage.set( - 'xpack.cases.commentDraft', + DRAFT_COMMENT_STORAGE_ID, JSON.stringify({ commentId: commentEditorContext?.editorId, comment: commentEditorContext?.value, @@ -243,9 +244,9 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ } let draftComment; - if (storage.get('xpack.cases.commentDraft')) { + if (storage.get(DRAFT_COMMENT_STORAGE_ID)) { try { - draftComment = JSON.parse(storage.get('xpack.cases.commentDraft')); + draftComment = JSON.parse(storage.get(DRAFT_COMMENT_STORAGE_ID)); if (draftComment.title) { setLensTitle(draftComment.title); } @@ -260,7 +261,17 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ - {node ? 'Edit Lens visualization' : 'Add Lens visualization'} + {editMode ? ( + + ) : ( + + )} @@ -273,7 +284,10 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ iconType="lensApp" fill > - {'Create visualization'} + @@ -283,7 +297,10 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ iconType="folderOpen" onClick={() => setShowLensSavedObjectsModal(true)} > - {'Add from library'} + @@ -292,7 +309,12 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ @@ -305,7 +327,10 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ isDisabled={!lens?.canUseEditor() || lensEmbeddableAttributes === null} onClick={handleEditInLensClick} > - {`Edit in Lens`} + @@ -321,7 +346,17 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ {'Cancel'} - {editMode ? 'Update' : 'Add to a Case'} + {editMode ? ( + + ) : ( + + )} @@ -337,7 +372,9 @@ export const LensEditor = React.memo(LensEditorComponent); export const plugin: LensEuiMarkdownEditorUiPlugin = { name: ID, button: { - label: i18n.INSERT_LENS, + label: i18n.translate('xpack.observability.markdownEditor.plugins.lens.insertLensButtonLabel', { + defaultMessage: 'Insert lens visualization', + }), iconType: 'lensApp', }, helpText: ( diff --git a/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/translations.ts b/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/translations.ts deleted file mode 100644 index 476a4bfdfe0bc..0000000000000 --- a/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/translations.ts +++ /dev/null @@ -1,55 +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. - */ - -import { i18n } from '@kbn/i18n'; - -export const INSERT_LENS = i18n.translate( - 'xpack.securitySolution.markdownEditor.plugins.lens.insertLensButtonLabel', - { - defaultMessage: 'Insert lens link', - } -); - -export const LENS_ID = (lensId: string) => - i18n.translate('xpack.securitySolution.markdownEditor.plugins.lens.toolTip.lensId', { - defaultMessage: 'Lens id: { lensId }', - values: { - lensId, - }, - }); - -export const NO_LENS_NAME_FOUND = i18n.translate( - 'xpack.securitySolution.markdownEditor.plugins.lens.noLensNameFoundErrorMsg', - { - defaultMessage: 'No lens name found', - } -); - -export const NO_LENS_ID_FOUND = i18n.translate( - 'xpack.securitySolution.markdownEditor.plugins.lens.noLensIdFoundErrorMsg', - { - defaultMessage: 'No lens id found', - } -); - -export const LENS_URL_IS_NOT_VALID = (lensUrl: string) => - i18n.translate( - 'xpack.securitySolution.markdownEditor.plugins.lens.toolTip.lensUrlIsNotValidErrorMsg', - { - defaultMessage: 'Lens URL is not valid => {lensUrl}', - values: { - lensUrl, - }, - } - ); - -export const NO_PARENTHESES = i18n.translate( - 'xpack.securitySolution.markdownEditor.plugins.lens.noParenthesesErrorMsg', - { - defaultMessage: 'Expected left parentheses', - } -); diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/constants.ts b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/constants.ts index a88051d8e4542..05826f73fe007 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/constants.ts +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/constants.ts @@ -8,3 +8,4 @@ export const ID = 'lens'; export const PREFIX = `[`; export const LENS_VISUALIZATION_HEIGHT = 200; +export const DRAFT_COMMENT_STORAGE_ID = 'xpack.cases.commentDraft'; diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx index ef0f450e2d053..968e0a53684bc 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx @@ -12,7 +12,9 @@ import { EuiModalHeader, EuiModalHeaderTitle, } from '@elastic/eui'; -import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import React, { useMemo } from 'react'; import { SavedObjectFinderUi, @@ -32,12 +34,35 @@ const LensSavedObjectsModalComponent: React.FC = ({ }) => { const { savedObjects, uiSettings } = useKibana().services; + const savedObjectMetaData = useMemo( + () => [ + { + type: 'lens', + getIconForSavedObject: () => 'lensApp', + name: i18n.translate( + 'xpack.securitySolution.markdownEditor.plugins.lens.insertLensSavedObjectModal.searchSelection.savedObjectType.lens', + { + defaultMessage: 'Lens', + } + ), + includeFields: ['*'], + }, + ], + [] + ); + return ( -

{'Modal title'}

+

+ {' '} + +

@@ -47,29 +72,12 @@ const LensSavedObjectsModalComponent: React.FC = ({ onChoose={onChoose} showFilter noItemsMessage={ - 'No matching lens found.' - - // i18n.translate( - // 'xpack.transform.newTransform.searchSelection.notFoundLabel', - // { - // defaultMessage: 'No matching lens found.', - // } - // ) + } - savedObjectMetaData={[ - { - type: 'lens', - getIconForSavedObject: () => 'lensApp', - name: 'Lens', - includeFields: ['*'], - // i18n.translate( - // 'xpack.transform.newTransform.searchSelection.savedObjectType.search', - // { - // defaultMessage: 'Lens', - // } - // ), - }, - ]} + savedObjectMetaData={savedObjectMetaData} fixedPageSize={10} uiSettings={uiSettings} savedObjects={savedObjects} diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx index 8cdbdf9d45800..bd0b22abc7acd 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx @@ -24,14 +24,15 @@ import { EuiMarkdownAstNodePosition, } from '@elastic/eui'; import React, { useCallback, useContext, useMemo, useEffect, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; import moment from 'moment'; import { useLocation } from 'react-router-dom'; import type { TypedLensByValueInput } from '../../../../../../../lens/public'; import { useKibana } from '../../../../lib/kibana'; import { LensMarkDownRenderer } from './processor'; -import { ID } from './constants'; -import * as i18n from './translations'; +import { DRAFT_COMMENT_STORAGE_ID, ID } from './constants'; import { CommentEditorContext } from './context'; import { LensSavedObjectsModal } from './lens_saved_objects_modal'; import { ModalContainer } from './modal_container'; @@ -98,9 +99,9 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ const handleAdd = useCallback(() => { let draftComment; - if (storage.get('xpack.cases.commentDraft')) { + if (storage.get(DRAFT_COMMENT_STORAGE_ID)) { try { - draftComment = JSON.parse(storage.get('xpack.cases.commentDraft')); + draftComment = JSON.parse(storage.get(DRAFT_COMMENT_STORAGE_ID)); // eslint-disable-next-line no-empty } catch (e) {} } @@ -113,7 +114,7 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ if (!node && draftComment.position) { markdownContext.replaceNode( draftComment.position, - `!{lens${JSON.stringify({ + `!{${ID}${JSON.stringify({ startDate, endDate, title: lensTitle, @@ -127,7 +128,7 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ if (lensEmbeddableAttributes) { onSave( - `!{lens${JSON.stringify({ + `!{${ID}${JSON.stringify({ startDate, endDate, title: lensTitle, @@ -159,7 +160,7 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ const handleEditInLensClick = useCallback(async () => { storage.set( - 'xpack.cases.commentDraft', + DRAFT_COMMENT_STORAGE_ID, JSON.stringify({ commentId: commentEditorContext?.editorId, comment: commentEditorContext?.value, @@ -242,9 +243,9 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ } let draftComment; - if (storage.get('xpack.cases.commentDraft')) { + if (storage.get(DRAFT_COMMENT_STORAGE_ID)) { try { - draftComment = JSON.parse(storage.get('xpack.cases.commentDraft')); + draftComment = JSON.parse(storage.get(DRAFT_COMMENT_STORAGE_ID)); if (draftComment.title) { setLensTitle(draftComment.title); } @@ -259,7 +260,17 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ - {node ? 'Edit Lens visualization' : 'Add Lens visualization'} + {editMode ? ( + + ) : ( + + )} @@ -272,7 +283,10 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ iconType="lensApp" fill > - {'Create visualization'} + @@ -282,7 +296,10 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ iconType="folderOpen" onClick={() => setShowLensSavedObjectsModal(true)} > - {'Add from library'} + @@ -291,7 +308,12 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ @@ -304,7 +326,10 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ isDisabled={!lens?.canUseEditor() || lensEmbeddableAttributes === null} onClick={handleEditInLensClick} > - {`Edit in Lens`} + @@ -320,7 +345,17 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ {'Cancel'} - {editMode ? 'Update' : 'Add to a Case'} + {editMode ? ( + + ) : ( + + )} @@ -336,7 +371,12 @@ export const LensEditor = React.memo(LensEditorComponent); export const plugin: LensEuiMarkdownEditorUiPlugin = { name: ID, button: { - label: i18n.INSERT_LENS, + label: i18n.translate( + 'xpack.securitySolution.markdownEditor.plugins.lens.insertLensButtonLabel', + { + defaultMessage: 'Insert lens visualization', + } + ), iconType: 'lensApp', }, helpText: ( diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/translations.ts b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/translations.ts deleted file mode 100644 index 476a4bfdfe0bc..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/translations.ts +++ /dev/null @@ -1,55 +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. - */ - -import { i18n } from '@kbn/i18n'; - -export const INSERT_LENS = i18n.translate( - 'xpack.securitySolution.markdownEditor.plugins.lens.insertLensButtonLabel', - { - defaultMessage: 'Insert lens link', - } -); - -export const LENS_ID = (lensId: string) => - i18n.translate('xpack.securitySolution.markdownEditor.plugins.lens.toolTip.lensId', { - defaultMessage: 'Lens id: { lensId }', - values: { - lensId, - }, - }); - -export const NO_LENS_NAME_FOUND = i18n.translate( - 'xpack.securitySolution.markdownEditor.plugins.lens.noLensNameFoundErrorMsg', - { - defaultMessage: 'No lens name found', - } -); - -export const NO_LENS_ID_FOUND = i18n.translate( - 'xpack.securitySolution.markdownEditor.plugins.lens.noLensIdFoundErrorMsg', - { - defaultMessage: 'No lens id found', - } -); - -export const LENS_URL_IS_NOT_VALID = (lensUrl: string) => - i18n.translate( - 'xpack.securitySolution.markdownEditor.plugins.lens.toolTip.lensUrlIsNotValidErrorMsg', - { - defaultMessage: 'Lens URL is not valid => {lensUrl}', - values: { - lensUrl, - }, - } - ); - -export const NO_PARENTHESES = i18n.translate( - 'xpack.securitySolution.markdownEditor.plugins.lens.noParenthesesErrorMsg', - { - defaultMessage: 'Expected left parentheses', - } -); From 0891047543c6406d6374dbaa02c92e6e3652d813 Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Tue, 29 Jun 2021 15:03:02 +0200 Subject: [PATCH 18/41] add feature flag --- x-pack/plugins/cases/common/ui/types.ts | 6 ++++++ .../cases/public/common/lib/kibana/services.ts | 17 ++++++++++++++++- .../components/markdown_editor/use_plugins.ts | 6 ++++-- x-pack/plugins/cases/public/plugin.ts | 7 ++++--- x-pack/plugins/cases/server/config.ts | 3 +++ x-pack/plugins/cases/server/index.ts | 3 +++ 6 files changed, 36 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/cases/common/ui/types.ts b/x-pack/plugins/cases/common/ui/types.ts index 3edbd3443ffc1..bf4ec0da6ee56 100644 --- a/x-pack/plugins/cases/common/ui/types.ts +++ b/x-pack/plugins/cases/common/ui/types.ts @@ -18,6 +18,12 @@ import { UserActionField, } from '../api'; +export interface CasesUiConfigType { + markdownPlugins: { + lens: boolean; + }; +} + export const StatusAll = 'all' as const; export type StatusAllType = typeof StatusAll; diff --git a/x-pack/plugins/cases/public/common/lib/kibana/services.ts b/x-pack/plugins/cases/public/common/lib/kibana/services.ts index 94487bd3ca5e9..33b61637c905d 100644 --- a/x-pack/plugins/cases/public/common/lib/kibana/services.ts +++ b/x-pack/plugins/cases/public/common/lib/kibana/services.ts @@ -6,16 +6,23 @@ */ import { CoreStart } from 'kibana/public'; +import { CasesUiConfigType } from '../../../../common/ui/types'; type GlobalServices = Pick; export class KibanaServices { private static kibanaVersion?: string; private static services?: GlobalServices; + private static config?: CasesUiConfigType; - public static init({ http, kibanaVersion }: GlobalServices & { kibanaVersion: string }) { + public static init({ + http, + kibanaVersion, + config, + }: GlobalServices & { kibanaVersion: string } & { config: any }) { this.services = { http }; this.kibanaVersion = kibanaVersion; + this.config = config; } public static get(): GlobalServices { @@ -34,6 +41,14 @@ export class KibanaServices { return this.kibanaVersion; } + public static getConfig() { + if (!this.config) { + this.throwUninitializedError(); + } + + return this.config; + } + private static throwUninitializedError(): never { throw new Error( 'Kibana services not initialized - are you trying to import this module from outside of the Cases app?' diff --git a/x-pack/plugins/cases/public/components/markdown_editor/use_plugins.ts b/x-pack/plugins/cases/public/components/markdown_editor/use_plugins.ts index 0a6deb1fab7ec..9f9ca9894c47c 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/use_plugins.ts +++ b/x-pack/plugins/cases/public/components/markdown_editor/use_plugins.ts @@ -14,8 +14,10 @@ import { useMemo } from 'react'; import { useTimelineContext } from '../timeline_context/use_timeline_context'; import { TemporaryProcessingPluginsType } from './types'; import { useCasesLensIntegrationContext } from '../lens_context/use_lens_context'; +import { KibanaServices } from '../../common/lib/kibana'; export const usePlugins = () => { + const kibanaConfig = KibanaServices.getConfig(); const timelinePlugins = useTimelineContext()?.editor_plugins; const lensPlugins = useCasesLensIntegrationContext()?.editor_plugins; @@ -33,7 +35,7 @@ export const usePlugins = () => { processingPlugins[1][1].components.timeline = timelinePlugins.processingPluginRenderer; } - if (lensPlugins) { + if (kibanaConfig?.markdownPlugins?.lens && lensPlugins) { uiPlugins.push(lensPlugins.uiPlugin); parsingPlugins.push(lensPlugins.parsingPlugin); @@ -47,5 +49,5 @@ export const usePlugins = () => { parsingPlugins, processingPlugins, }; - }, [lensPlugins, timelinePlugins]); + }, [kibanaConfig?.markdownPlugins?.lens, lensPlugins, timelinePlugins]); }; diff --git a/x-pack/plugins/cases/public/plugin.ts b/x-pack/plugins/cases/public/plugin.ts index 5bfdf9b8b9509..2b4fb40545548 100644 --- a/x-pack/plugins/cases/public/plugin.ts +++ b/x-pack/plugins/cases/public/plugin.ts @@ -17,7 +17,7 @@ import { getRecentCasesLazy, getAllCasesSelectorModalLazy, } from './methods'; -import { ENABLE_CASE_CONNECTOR } from '../common'; +import { CasesUiConfigType, ENABLE_CASE_CONNECTOR } from '../common'; /** * @public @@ -26,7 +26,7 @@ import { ENABLE_CASE_CONNECTOR } from '../common'; export class CasesUiPlugin implements Plugin { private kibanaVersion: string; - constructor(initializerContext: PluginInitializerContext) { + constructor(private readonly initializerContext: PluginInitializerContext) { this.kibanaVersion = initializerContext.env.packageInfo.version; } public setup(core: CoreSetup, plugins: SetupPlugins) { @@ -36,7 +36,8 @@ export class CasesUiPlugin implements Plugin(); + KibanaServices.init({ ...core, ...plugins, kibanaVersion: this.kibanaVersion, config }); return { /** * Get the all cases table diff --git a/x-pack/plugins/cases/server/config.ts b/x-pack/plugins/cases/server/config.ts index 7679a5a389051..7a81c47937a6c 100644 --- a/x-pack/plugins/cases/server/config.ts +++ b/x-pack/plugins/cases/server/config.ts @@ -9,6 +9,9 @@ import { schema, TypeOf } from '@kbn/config-schema'; export const ConfigSchema = schema.object({ enabled: schema.boolean({ defaultValue: true }), + markdownPlugins: schema.object({ + lens: schema.boolean({ defaultValue: true }), + }), }); export type ConfigType = TypeOf; diff --git a/x-pack/plugins/cases/server/index.ts b/x-pack/plugins/cases/server/index.ts index 0e4554572aad9..935f82e54545d 100644 --- a/x-pack/plugins/cases/server/index.ts +++ b/x-pack/plugins/cases/server/index.ts @@ -13,6 +13,9 @@ import { CasePlugin } from './plugin'; export { CaseRequestContext } from './types'; export const config: PluginConfigDescriptor = { schema: ConfigSchema, + exposeToBrowser: { + markdownPlugins: true, + }, deprecations: ({ renameFromRoot }) => [ renameFromRoot('xpack.case.enabled', 'xpack.cases.enabled'), ], From c422e592067c67428ab940aaffb9aa127cc8f216 Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Tue, 29 Jun 2021 16:43:47 +0200 Subject: [PATCH 19/41] move everything to cases --- x-pack/plugins/cases/kibana.json | 4 + .../public/common/lib/kibana/services.ts | 4 - .../public/components/case_view/index.tsx | 43 +- .../public/components/lens_context/index.tsx | 44 -- .../lens_context/use_lens_context.ts | 11 - .../components/markdown_editor}/context.tsx | 0 .../components/markdown_editor/editor.tsx | 30 +- .../markdown_editor/plugins/lens/constants.ts | 0 .../markdown_editor/plugins/lens/helpers.ts | 2 +- .../markdown_editor/plugins/lens/index.ts | 8 +- .../plugins/lens/lens_saved_objects_modal.tsx | 5 +- .../plugins/lens/modal_container.tsx | 0 .../markdown_editor/plugins/lens/parser.ts | 0 .../markdown_editor/plugins/lens/plugin.tsx | 19 +- .../plugins/lens/processor.tsx | 8 +- .../components/markdown_editor/use_plugins.ts | 13 +- x-pack/plugins/cases/public/types.ts | 4 + x-pack/plugins/cases/server/config.ts | 2 +- x-pack/plugins/observability/kibana.json | 3 +- .../components/app/cases/case_view/index.tsx | 9 - .../markdown_editor/plugins/lens/helpers.ts | 90 ---- .../markdown_editor/plugins/lens/index.ts | 18 - .../plugins/lens/processor.tsx | 142 ------- x-pack/plugins/security_solution/kibana.json | 2 +- .../cases/components/case_view/index.tsx | 9 - .../public/cases/components/create/index.tsx | 9 - .../markdown_editor/plugins/lens/constants.ts | 11 - .../markdown_editor/plugins/lens/context.tsx | 13 - .../plugins/lens/lens_saved_objects_modal.tsx | 91 ---- .../plugins/lens/modal_container.tsx | 16 - .../markdown_editor/plugins/lens/parser.ts | 76 ---- .../markdown_editor/plugins/lens/plugin.tsx | 388 ------------------ 32 files changed, 65 insertions(+), 1009 deletions(-) delete mode 100644 x-pack/plugins/cases/public/components/lens_context/index.tsx delete mode 100644 x-pack/plugins/cases/public/components/lens_context/use_lens_context.ts rename x-pack/plugins/{observability/public/components/app/cases/case_view/markdown_editor/plugins/lens => cases/public/components/markdown_editor}/context.tsx (100%) rename x-pack/plugins/{observability/public/components/app/cases/case_view => cases/public/components}/markdown_editor/plugins/lens/constants.ts (100%) rename x-pack/plugins/{security_solution/public/common => cases/public}/components/markdown_editor/plugins/lens/helpers.ts (98%) rename x-pack/plugins/{security_solution/public/common => cases/public}/components/markdown_editor/plugins/lens/index.ts (69%) rename x-pack/plugins/{observability/public/components/app/cases/case_view => cases/public/components}/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx (92%) rename x-pack/plugins/{observability/public/components/app/cases/case_view => cases/public/components}/markdown_editor/plugins/lens/modal_container.tsx (100%) rename x-pack/plugins/{observability/public/components/app/cases/case_view => cases/public/components}/markdown_editor/plugins/lens/parser.ts (100%) rename x-pack/plugins/{observability/public/components/app/cases/case_view => cases/public/components}/markdown_editor/plugins/lens/plugin.tsx (94%) rename x-pack/plugins/{security_solution/public/common => cases/public}/components/markdown_editor/plugins/lens/processor.tsx (93%) delete mode 100644 x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/helpers.ts delete mode 100644 x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/index.ts delete mode 100644 x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/processor.tsx delete mode 100644 x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/constants.ts delete mode 100644 x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/context.tsx delete mode 100644 x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx delete mode 100644 x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/modal_container.tsx delete mode 100644 x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/parser.ts delete mode 100644 x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx diff --git a/x-pack/plugins/cases/kibana.json b/x-pack/plugins/cases/kibana.json index f72f0e012bd80..ebac6295166df 100644 --- a/x-pack/plugins/cases/kibana.json +++ b/x-pack/plugins/cases/kibana.json @@ -20,11 +20,15 @@ "requiredPlugins":[ "actions", "esUiShared", + "lens", "features", "kibanaReact", "kibanaUtils", "triggersActionsUi" ], + "requiredBundles": [ + "savedObjects" + ], "server":true, "ui":true, "version":"8.0.0" diff --git a/x-pack/plugins/cases/public/common/lib/kibana/services.ts b/x-pack/plugins/cases/public/common/lib/kibana/services.ts index 33b61637c905d..e3a75d468ad02 100644 --- a/x-pack/plugins/cases/public/common/lib/kibana/services.ts +++ b/x-pack/plugins/cases/public/common/lib/kibana/services.ts @@ -42,10 +42,6 @@ export class KibanaServices { } public static getConfig() { - if (!this.config) { - this.throwUninitializedError(); - } - return this.config; } diff --git a/x-pack/plugins/cases/public/components/case_view/index.tsx b/x-pack/plugins/cases/public/components/case_view/index.tsx index 6b067c44aed7d..0e49173a6b838 100644 --- a/x-pack/plugins/cases/public/components/case_view/index.tsx +++ b/x-pack/plugins/cases/public/components/case_view/index.tsx @@ -40,7 +40,6 @@ import { CasesNavigation } from '../links'; import { OwnerProvider } from '../owner_context'; import { getConnectorById } from '../utils'; import { DoesNotExist } from './does_not_exist'; -import { CasesLensIntegration, CasesLensIntegrationProvider } from '../lens_context'; const gutterTimeline = '70px'; // seems to be a timeline reference from the original file @@ -64,7 +63,6 @@ export interface CaseViewComponentProps { } export interface CaseViewProps extends CaseViewComponentProps { - lensIntegration?: CasesLensIntegration; onCaseDataSuccess?: (data: Case) => void; timelineIntegration?: CasesTimelineIntegration; } @@ -505,7 +503,6 @@ export const CaseView = React.memo( caseId, configureCasesNavigation, getCaseDetailHrefWithCommentId, - lensIntegration, onCaseDataSuccess, onComponentInitialized, ruleDetailsNavigation, @@ -536,27 +533,25 @@ export const CaseView = React.memo( return ( data && ( - - - - - + + + ) ); diff --git a/x-pack/plugins/cases/public/components/lens_context/index.tsx b/x-pack/plugins/cases/public/components/lens_context/index.tsx deleted file mode 100644 index 208e2446a8ead..0000000000000 --- a/x-pack/plugins/cases/public/components/lens_context/index.tsx +++ /dev/null @@ -1,44 +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. - */ - -import React, { useState } from 'react'; -import { EuiMarkdownEditorUiPlugin } from '@elastic/eui'; -import { Plugin } from 'unified'; -import { TypedLensByValueInput } from '../../../../lens/public'; - -interface LensProcessingPluginRendererProps { - attributes: TypedLensByValueInput['attributes'] | null; - id?: string | null; - title?: string | null; - startDate?: string | null; - endDate?: string | null; - viewMode?: boolean | undefined; -} - -export interface CasesLensIntegration { - editor_context: any; - editor_plugins: { - parsingPlugin: Plugin; - processingPluginRenderer: React.FC; - uiPlugin: EuiMarkdownEditorUiPlugin; - }; -} - -// This context is available to all children of the stateful_event component where the provider is currently set -export const CasesLensIntegrationContext = React.createContext(null); - -export const CasesLensIntegrationProvider: React.FC<{ - lensIntegration?: CasesLensIntegration; -}> = ({ children, lensIntegration }) => { - const [activeLensIntegration] = useState(lensIntegration ?? null); - - return ( - - {children} - - ); -}; diff --git a/x-pack/plugins/cases/public/components/lens_context/use_lens_context.ts b/x-pack/plugins/cases/public/components/lens_context/use_lens_context.ts deleted file mode 100644 index c0d5d7eb29121..0000000000000 --- a/x-pack/plugins/cases/public/components/lens_context/use_lens_context.ts +++ /dev/null @@ -1,11 +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. - */ - -import { useContext } from 'react'; -import { CasesLensIntegrationContext } from '.'; - -export const useCasesLensIntegrationContext = () => useContext(CasesLensIntegrationContext); diff --git a/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/context.tsx b/x-pack/plugins/cases/public/components/markdown_editor/context.tsx similarity index 100% rename from x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/context.tsx rename to x-pack/plugins/cases/public/components/markdown_editor/context.tsx diff --git a/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx b/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx index 3c217a04dbace..6c015c1ce219c 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx @@ -18,12 +18,12 @@ import { PluggableList } from 'unified'; import { EuiMarkdownEditor, EuiMarkdownEditorUiPlugin } from '@elastic/eui'; import { ContextShape } from '@elastic/eui/src/components/markdown_editor/markdown_context'; import { usePlugins } from './use_plugins'; -import { useCasesLensIntegrationContext } from '../lens_context/use_lens_context'; +import { CommentEditorContext } from './context'; interface MarkdownEditorProps { ariaLabel: string; dataTestSubj?: string; - editorId?: string; + editorId: string; height?: number; onChange: (content: string) => void; parsingPlugins?: PluggableList; @@ -47,8 +47,6 @@ const MarkdownEditorComponent = forwardRef(null); // @ts-expect-error update types @@ -82,20 +80,16 @@ const MarkdownEditorComponent = forwardRef ); - if (CasesLensIntegrationContextProvider) { - return ( - - {editor} - - ); - } - - return editor; + return ( + + {editor} + + ); } ); diff --git a/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/constants.ts b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/constants.ts similarity index 100% rename from x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/constants.ts rename to x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/constants.ts diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/helpers.ts b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/helpers.ts similarity index 98% rename from x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/helpers.ts rename to x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/helpers.ts index 05d789d515eeb..f4549c246e39c 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/helpers.ts +++ b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/helpers.ts @@ -10,7 +10,7 @@ import type { TypedLensByValueInput, PersistedIndexPatternLayer, XYState, -} from '../../../../../../../lens/public'; +} from '../../../../../../lens/public'; // Generate a Lens state based on some app-specific input parameters. // `TypedLensByValueInput` can be used for type-safety - it uses the same interfaces as Lens-internal code. diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/index.ts b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/index.ts similarity index 69% rename from x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/index.ts rename to x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/index.ts index b0320954c97f0..aa01ac0555c1e 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/index.ts +++ b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/index.ts @@ -8,11 +8,5 @@ import { plugin } from './plugin'; import { LensParser } from './parser'; import { LensMarkDownRenderer } from './processor'; -import { CommentEditorContext } from './context'; -export { - CommentEditorContext as context, - plugin, - LensParser as parser, - LensMarkDownRenderer as renderer, -}; +export { plugin, LensParser as parser, LensMarkDownRenderer as renderer }; diff --git a/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx similarity index 92% rename from x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx rename to x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx index 19da69327b0c9..56f57ef284098 100644 --- a/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx @@ -19,8 +19,8 @@ import React, { useMemo } from 'react'; import { SavedObjectFinderUi, SavedObjectFinderUiProps, -} from '../../../../../../../../../../../src/plugins/saved_objects/public'; -import { useKibana } from '../../../../../../../utils/kibana_react'; +} from '../../../../../../../../src/plugins/saved_objects/public'; +import { useKibana } from '../../../../common/lib/kibana'; import { ModalContainer } from './modal_container'; interface LensSavedObjectsModalProps { @@ -28,7 +28,6 @@ interface LensSavedObjectsModalProps { onChoose: SavedObjectFinderUiProps['onChoose']; } -// eslint-disable-next-line react/function-component-definition const LensSavedObjectsModalComponent: React.FC = ({ onClose, onChoose, diff --git a/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/modal_container.tsx b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/modal_container.tsx similarity index 100% rename from x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/modal_container.tsx rename to x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/modal_container.tsx diff --git a/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/parser.ts b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/parser.ts similarity index 100% rename from x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/parser.ts rename to x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/parser.ts diff --git a/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/plugin.tsx b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx similarity index 94% rename from x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/plugin.tsx rename to x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx index 983fe58797a0f..8ca0323a89642 100644 --- a/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/plugin.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx @@ -29,18 +29,18 @@ import { FormattedMessage } from '@kbn/i18n/react'; import moment from 'moment'; import { useLocation } from 'react-router-dom'; -import type { TypedLensByValueInput } from '../../../../../../../../../lens/public'; -import { useKibana } from '../../../../../../../utils/kibana_react'; +import type { TypedLensByValueInput } from '../../../../../../lens/public'; +import { useKibana } from '../../../../common/lib/kibana'; import { LensMarkDownRenderer } from './processor'; import { DRAFT_COMMENT_STORAGE_ID, ID } from './constants'; -import { CommentEditorContext } from './context'; +import { CommentEditorContext } from '../../context'; import { LensSavedObjectsModal } from './lens_saved_objects_modal'; import { ModalContainer } from './modal_container'; import { getLensAttributes } from './helpers'; import type { EmbeddablePackageState, EmbeddableInput, -} from '../../../../../../../../../../../src/plugins/embeddable/public'; +} from '../../../../../../../../src/plugins/embeddable/public'; type LensIncomingEmbeddablePackage = Omit & { input: Omit & { @@ -57,7 +57,6 @@ type LensEuiMarkdownEditorUiPlugin = EuiMarkdownEditorUiPlugin<{ attributes: TypedLensByValueInput['attributes']; }>; -// eslint-disable-next-line react/function-component-definition const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ node, onCancel, @@ -264,12 +263,12 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ {editMode ? ( ) : ( )} @@ -299,7 +298,7 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ > @@ -329,7 +328,7 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ > @@ -373,7 +372,7 @@ export const plugin: LensEuiMarkdownEditorUiPlugin = { name: ID, button: { label: i18n.translate('xpack.observability.markdownEditor.plugins.lens.insertLensButtonLabel', { - defaultMessage: 'Insert lens visualization', + defaultMessage: 'Insert visualization', }), iconType: 'lensApp', }, diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/processor.tsx b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/processor.tsx similarity index 93% rename from x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/processor.tsx rename to x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/processor.tsx index c7f4241f9dd1f..502c172cdf0fa 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/processor.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/processor.tsx @@ -11,9 +11,9 @@ import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiText, EuiSpacer } from '@elast import styled from 'styled-components'; import { useLocation } from 'react-router-dom'; -import { createGlobalStyle } from '../../../../../../../../../src/plugins/kibana_react/common'; -import { TypedLensByValueInput } from '../../../../../../../lens/public'; -import { useKibana } from '../../../../lib/kibana'; +import { createGlobalStyle } from '../../../../../../../../src/plugins/kibana_react/common'; +import { TypedLensByValueInput } from '../../../../../../lens/public'; +import { useKibana } from '../../../../common/lib/kibana'; import { LENS_VISUALIZATION_HEIGHT } from './constants'; const Container = styled.div` @@ -111,7 +111,7 @@ const LensMarkDownRendererComponent: React.FC = ({ isDisabled={!canUseEditor() || !attributes} onClick={handleClick} > - {`Open in Lens`} + {`Open visualization`} ) : null} diff --git a/x-pack/plugins/cases/public/components/markdown_editor/use_plugins.ts b/x-pack/plugins/cases/public/components/markdown_editor/use_plugins.ts index 9f9ca9894c47c..4249a014339bd 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/use_plugins.ts +++ b/x-pack/plugins/cases/public/components/markdown_editor/use_plugins.ts @@ -13,13 +13,12 @@ import { import { useMemo } from 'react'; import { useTimelineContext } from '../timeline_context/use_timeline_context'; import { TemporaryProcessingPluginsType } from './types'; -import { useCasesLensIntegrationContext } from '../lens_context/use_lens_context'; import { KibanaServices } from '../../common/lib/kibana'; +import * as lensMarkdownPlugin from './plugins/lens'; export const usePlugins = () => { const kibanaConfig = KibanaServices.getConfig(); const timelinePlugins = useTimelineContext()?.editor_plugins; - const lensPlugins = useCasesLensIntegrationContext()?.editor_plugins; return useMemo(() => { const uiPlugins = getDefaultEuiMarkdownUiPlugins(); @@ -35,13 +34,13 @@ export const usePlugins = () => { processingPlugins[1][1].components.timeline = timelinePlugins.processingPluginRenderer; } - if (kibanaConfig?.markdownPlugins?.lens && lensPlugins) { - uiPlugins.push(lensPlugins.uiPlugin); + if (kibanaConfig?.markdownPlugins?.lens) { + uiPlugins.push(lensMarkdownPlugin.plugin); - parsingPlugins.push(lensPlugins.parsingPlugin); + parsingPlugins.push(lensMarkdownPlugin.parser); // This line of code is TS-compatible and it will break if [1][1] change in the future. - processingPlugins[1][1].components.lens = lensPlugins.processingPluginRenderer; + processingPlugins[1][1].components.lens = lensMarkdownPlugin.renderer; } return { @@ -49,5 +48,5 @@ export const usePlugins = () => { parsingPlugins, processingPlugins, }; - }, [kibanaConfig?.markdownPlugins?.lens, lensPlugins, timelinePlugins]); + }, [kibanaConfig?.markdownPlugins?.lens, timelinePlugins]); }; diff --git a/x-pack/plugins/cases/public/types.ts b/x-pack/plugins/cases/public/types.ts index 5c7e57d0f7ffa..70363aa836e00 100644 --- a/x-pack/plugins/cases/public/types.ts +++ b/x-pack/plugins/cases/public/types.ts @@ -8,11 +8,13 @@ import { CoreStart } from 'kibana/public'; import { ReactElement } from 'react'; +import { LensPublicStart } from '../../lens/public'; import { SecurityPluginSetup } from '../../security/public'; import type { TriggersAndActionsUIPublicPluginSetup as TriggersActionsSetup, TriggersAndActionsUIPublicPluginStart as TriggersActionsStart, } from '../../triggers_actions_ui/public'; +import { DataPublicPluginStart } from '../../../../src/plugins/data/public'; import type { EmbeddableStart } from '../../../../src/plugins/embeddable/public'; import type { Storage } from '../../../../src/plugins/kibana_utils/public'; @@ -29,7 +31,9 @@ export interface SetupPlugins { } export interface StartPlugins { + data: DataPublicPluginStart; embeddable: EmbeddableStart; + lens: LensPublicStart; storage: Storage; triggersActionsUi: TriggersActionsStart; } diff --git a/x-pack/plugins/cases/server/config.ts b/x-pack/plugins/cases/server/config.ts index 7a81c47937a6c..317f15283e112 100644 --- a/x-pack/plugins/cases/server/config.ts +++ b/x-pack/plugins/cases/server/config.ts @@ -10,7 +10,7 @@ import { schema, TypeOf } from '@kbn/config-schema'; export const ConfigSchema = schema.object({ enabled: schema.boolean({ defaultValue: true }), markdownPlugins: schema.object({ - lens: schema.boolean({ defaultValue: true }), + lens: schema.boolean({ defaultValue: false }), }), }); diff --git a/x-pack/plugins/observability/kibana.json b/x-pack/plugins/observability/kibana.json index 8329ead209898..29975a7ad05a4 100644 --- a/x-pack/plugins/observability/kibana.json +++ b/x-pack/plugins/observability/kibana.json @@ -26,7 +26,6 @@ "requiredBundles": [ "data", "kibanaReact", - "kibanaUtils", - "savedObjects" + "kibanaUtils" ] } diff --git a/x-pack/plugins/observability/public/components/app/cases/case_view/index.tsx b/x-pack/plugins/observability/public/components/app/cases/case_view/index.tsx index 35c8ab2fdc401..9d18381573e32 100644 --- a/x-pack/plugins/observability/public/components/app/cases/case_view/index.tsx +++ b/x-pack/plugins/observability/public/components/app/cases/case_view/index.tsx @@ -18,7 +18,6 @@ import { Case } from '../../../../../../cases/common'; import { useFetchAlertData } from './helpers'; import { useKibana } from '../../../../utils/kibana_react'; import { useBreadcrumbs } from '../../../../hooks/use_breadcrumbs'; -import * as lensMarkdownPlugin from './markdown_editor/plugins/lens'; import { observabilityAppId } from '../../../../../common'; interface Props { @@ -117,13 +116,5 @@ export const CaseView = React.memo(({ caseId, subCaseId, userCanCrud }: Props) = subCaseId, useFetchAlertData, userCanCrud, - lensIntegration: { - editor_context: lensMarkdownPlugin.context, - editor_plugins: { - parsingPlugin: lensMarkdownPlugin.parser, - processingPluginRenderer: lensMarkdownPlugin.renderer, - uiPlugin: lensMarkdownPlugin.plugin, - }, - }, }); }); diff --git a/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/helpers.ts b/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/helpers.ts deleted file mode 100644 index acd1cc3a2725c..0000000000000 --- a/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/helpers.ts +++ /dev/null @@ -1,90 +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. - */ - -import type { IndexPattern } from 'src/plugins/data/public'; -import type { - TypedLensByValueInput, - PersistedIndexPatternLayer, - XYState, -} from '../../../../../../../../../lens/public'; - -// Generate a Lens state based on some app-specific input parameters. -// `TypedLensByValueInput` can be used for type-safety - it uses the same interfaces as Lens-internal code. -export const getLensAttributes = ( - defaultIndexPattern: IndexPattern | null -): TypedLensByValueInput['attributes'] => { - const dataLayer: PersistedIndexPatternLayer = { - columnOrder: ['col1', 'col2'], - columns: { - col2: { - dataType: 'number', - isBucketed: false, - label: 'Count of records', - operationType: 'count', - scale: 'ratio', - sourceField: 'Records', - }, - col1: { - dataType: 'date', - isBucketed: true, - label: '@timestamp', - operationType: 'date_histogram', - params: { interval: 'auto' }, - scale: 'interval', - sourceField: defaultIndexPattern?.timeFieldName!, - }, - }, - }; - - const xyConfig: XYState = { - axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true }, - fittingFunction: 'None', - gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true }, - layers: [ - { - accessors: ['col2'], - layerId: 'layer1', - seriesType: 'bar_stacked', - xAccessor: 'col1', - yConfig: [{ forAccessor: 'col2', color: undefined }], - }, - ], - legend: { isVisible: true, position: 'right' }, - preferredSeriesType: 'bar_stacked', - tickLabelsVisibilitySettings: { x: true, yLeft: true, yRight: true }, - valueLabels: 'hide', - }; - - return { - visualizationType: 'lnsXY', - title: '', - references: [ - { - id: defaultIndexPattern?.id!, - name: 'indexpattern-datasource-current-indexpattern', - type: 'index-pattern', - }, - { - id: defaultIndexPattern?.id!, - name: 'indexpattern-datasource-layer-layer1', - type: 'index-pattern', - }, - ], - state: { - datasourceStates: { - indexpattern: { - layers: { - layer1: dataLayer, - }, - }, - }, - filters: [], - query: { language: 'kuery', query: '' }, - visualization: xyConfig, - }, - }; -}; diff --git a/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/index.ts b/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/index.ts deleted file mode 100644 index b0320954c97f0..0000000000000 --- a/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/index.ts +++ /dev/null @@ -1,18 +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. - */ - -import { plugin } from './plugin'; -import { LensParser } from './parser'; -import { LensMarkDownRenderer } from './processor'; -import { CommentEditorContext } from './context'; - -export { - CommentEditorContext as context, - plugin, - LensParser as parser, - LensMarkDownRenderer as renderer, -}; diff --git a/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/processor.tsx b/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/processor.tsx deleted file mode 100644 index e54900ad30856..0000000000000 --- a/x-pack/plugins/observability/public/components/app/cases/case_view/markdown_editor/plugins/lens/processor.tsx +++ /dev/null @@ -1,142 +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. - */ - -import { first } from 'rxjs/operators'; -import React, { useCallback, useEffect, useState } from 'react'; -import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiText, EuiSpacer } from '@elastic/eui'; -import styled from 'styled-components'; -import { useLocation } from 'react-router-dom'; - -import { createGlobalStyle } from '../../../../../../../../../../../src/plugins/kibana_react/common'; -import { TypedLensByValueInput } from '../../../../../../../../../lens/public'; -import { useKibana } from '../../../../../../../utils/kibana_react'; -import { LENS_VISUALIZATION_HEIGHT } from './constants'; - -const Container = styled.div` - min-height: ${LENS_VISUALIZATION_HEIGHT}px; -`; - -// when displaying chart in modal the tooltip is render under the modal -const LensChartTooltipFix = createGlobalStyle` - div.euiOverlayMask.euiOverlayMask--aboveHeader ~ [id^='echTooltipPortal'] { - z-index: ${({ theme }) => theme.eui.euiZLevel7} !important; - } -`; - -interface LensMarkDownRendererProps { - attributes: TypedLensByValueInput['attributes'] | null; - id?: string | null; - title?: string | null; - startDate?: string | null; - endDate?: string | null; - viewMode?: boolean | undefined; -} - -// eslint-disable-next-line react/function-component-definition -const LensMarkDownRendererComponent: React.FC = ({ - attributes, - title, - startDate, - endDate, - viewMode = true, -}) => { - const location = useLocation(); - const { - application: { currentAppId$ }, - lens: { EmbeddableComponent, navigateToPrefilledEditor, canUseEditor }, - } = useKibana().services; - const [currentAppId, setCurrentAppId] = useState(undefined); - - const handleClick = useCallback(() => { - const options = viewMode - ? { - openInNewTab: true, - } - : { - originatingApp: currentAppId, - originatingPath: `${location.pathname}${location.search}`, - }; - - if (attributes) { - navigateToPrefilledEditor( - { - id: '', - timeRange: { - from: startDate ?? 'now-7d', - to: endDate ?? 'now', - mode: startDate ? 'absolute' : 'relative', - }, - attributes, - }, - options - ); - } - }, [ - attributes, - currentAppId, - endDate, - location.pathname, - location.search, - navigateToPrefilledEditor, - startDate, - viewMode, - ]); - - useEffect(() => { - const getCurrentAppId = async () => { - const appId = await currentAppId$.pipe(first()).toPromise(); - setCurrentAppId(appId); - }; - getCurrentAppId(); - }, [currentAppId$]); - - return ( - - {attributes ? ( - <> - - - -
{title}
-
-
- - {viewMode ? ( - - {`Open in Lens`} - - ) : null} - -
- - - - {attributes ? ( - - ) : null} - - - ) : null} -
- ); -}; - -export const LensMarkDownRenderer = React.memo(LensMarkDownRendererComponent); diff --git a/x-pack/plugins/security_solution/kibana.json b/x-pack/plugins/security_solution/kibana.json index 88a743d5006bd..2db0d7196048c 100644 --- a/x-pack/plugins/security_solution/kibana.json +++ b/x-pack/plugins/security_solution/kibana.json @@ -38,5 +38,5 @@ ], "server": true, "ui": true, - "requiredBundles": ["esUiShared", "fleet", "kibanaUtils", "kibanaReact", "lists", "ml", "savedObjects"] + "requiredBundles": ["esUiShared", "fleet", "kibanaUtils", "kibanaReact", "lists", "ml"] } diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx index 791700beef870..dec2d409b020d 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx @@ -38,7 +38,6 @@ import { SEND_ALERT_TO_TIMELINE } from './translations'; import { useInsertTimeline } from '../use_insert_timeline'; import { SpyRoute } from '../../../common/utils/route/spy_routes'; import * as timelineMarkdownPlugin from '../../../common/components/markdown_editor/plugins/timeline'; -import * as lensMarkdownPlugin from '../../../common/components/markdown_editor/plugins/lens'; import { CaseDetailsRefreshContext } from '../../../common/components/endpoint/host_isolation/endpoint_host_isolation_cases_context'; interface Props { @@ -220,14 +219,6 @@ export const CaseView = React.memo(({ caseId, subCaseId, userCanCrud }: Props) = }, }, getCaseDetailHrefWithCommentId, - lensIntegration: { - editor_context: lensMarkdownPlugin.context, - editor_plugins: { - parsingPlugin: lensMarkdownPlugin.parser, - processingPluginRenderer: lensMarkdownPlugin.renderer, - uiPlugin: lensMarkdownPlugin.plugin, - }, - }, onCaseDataSuccess, onComponentInitialized, ruleDetailsNavigation: { diff --git a/x-pack/plugins/security_solution/public/cases/components/create/index.tsx b/x-pack/plugins/security_solution/public/cases/components/create/index.tsx index 726228e0f4751..dfd53ae5cc0b0 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/create/index.tsx @@ -11,7 +11,6 @@ import { EuiPanel } from '@elastic/eui'; import { getCaseDetailsUrl, getCaseUrl } from '../../../common/components/link_to'; import { useKibana } from '../../../common/lib/kibana'; import * as timelineMarkdownPlugin from '../../../common/components/markdown_editor/plugins/timeline'; -// import * as lensMarkdownPlugin from '../../../common/components/markdown_editor/plugins/lens'; import { useInsertTimeline } from '../use_insert_timeline'; import { APP_ID, CASES_APP_ID } from '../../../../common/constants'; import { useGetUrlSearch } from '../../../common/components/navigation/use_get_url_search'; @@ -43,14 +42,6 @@ export const Create = React.memo(() => { {cases.getCreateCase({ onCancel: handleSetIsCancel, onSuccess, - // lensIntegration: { - // editor_context: lensMarkdownPlugin.context, - // editor_plugins: { - // parsingPlugin: lensMarkdownPlugin.parser, - // processingPluginRenderer: lensMarkdownPlugin.renderer, - // uiPlugin: lensMarkdownPlugin.plugin, - // }, - // }, timelineIntegration: { editor_plugins: { parsingPlugin: timelineMarkdownPlugin.parser, diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/constants.ts b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/constants.ts deleted file mode 100644 index 05826f73fe007..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/constants.ts +++ /dev/null @@ -1,11 +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. - */ - -export const ID = 'lens'; -export const PREFIX = `[`; -export const LENS_VISUALIZATION_HEIGHT = 200; -export const DRAFT_COMMENT_STORAGE_ID = 'xpack.cases.commentDraft'; diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/context.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/context.tsx deleted file mode 100644 index d7f5b0612cb73..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/context.tsx +++ /dev/null @@ -1,13 +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. - */ - -import React from 'react'; - -export const CommentEditorContext = React.createContext<{ - editorId: string; - value: string; -} | null>(null); diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx deleted file mode 100644 index 968e0a53684bc..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx +++ /dev/null @@ -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. - */ - -import { - EuiModal, - EuiModalProps, - EuiModalBody, - EuiModalHeader, - EuiModalHeaderTitle, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import React, { useMemo } from 'react'; - -import { - SavedObjectFinderUi, - SavedObjectFinderUiProps, -} from '../../../../../../../../../src/plugins/saved_objects/public'; -import { useKibana } from '../../../../lib/kibana'; -import { ModalContainer } from './modal_container'; - -interface LensSavedObjectsModalProps { - onClose: EuiModalProps['onClose']; - onChoose: SavedObjectFinderUiProps['onChoose']; -} - -const LensSavedObjectsModalComponent: React.FC = ({ - onClose, - onChoose, -}) => { - const { savedObjects, uiSettings } = useKibana().services; - - const savedObjectMetaData = useMemo( - () => [ - { - type: 'lens', - getIconForSavedObject: () => 'lensApp', - name: i18n.translate( - 'xpack.securitySolution.markdownEditor.plugins.lens.insertLensSavedObjectModal.searchSelection.savedObjectType.lens', - { - defaultMessage: 'Lens', - } - ), - includeFields: ['*'], - }, - ], - [] - ); - - return ( - - - - -

- {' '} - -

-
-
- - - - } - savedObjectMetaData={savedObjectMetaData} - fixedPageSize={10} - uiSettings={uiSettings} - savedObjects={savedObjects} - /> - -
-
- ); -}; - -export const LensSavedObjectsModal = React.memo(LensSavedObjectsModalComponent); diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/modal_container.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/modal_container.tsx deleted file mode 100644 index 0f70e80deed41..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/modal_container.tsx +++ /dev/null @@ -1,16 +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. - */ - -import styled from 'styled-components'; - -export const ModalContainer = styled.div` - width: ${({ theme }) => theme.eui.euiBreakpoints.m}; - - .euiModalBody { - min-height: 300px; - } -`; diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/parser.ts b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/parser.ts deleted file mode 100644 index 5d4ec22b93e2b..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/parser.ts +++ /dev/null @@ -1,76 +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. - */ - -import { Plugin } from 'unified'; -import { RemarkTokenizer } from '@elastic/eui'; - -export const LensParser: Plugin = function () { - const Parser = this.Parser; - const tokenizers = Parser.prototype.blockTokenizers; - const methods = Parser.prototype.blockMethods; - - const tokenizeLens: RemarkTokenizer = function (eat, value, silent) { - if (value.startsWith('!{lens') === false) return false; - - const nextChar = value[6]; - - if (nextChar !== '{' && nextChar !== '}') return false; // this isn't actually a lens - - if (silent) { - return true; - } - - // is there a configuration? - const hasConfiguration = nextChar === '{'; - - let match = '!{lens'; - let configuration = {}; - - if (hasConfiguration) { - let configurationString = ''; - - let openObjects = 0; - - for (let i = 6; i < value.length; i++) { - const char = value[i]; - if (char === '{') { - openObjects++; - configurationString += char; - } else if (char === '}') { - openObjects--; - if (openObjects === -1) { - break; - } - configurationString += char; - } else { - configurationString += char; - } - } - - match += configurationString; - try { - configuration = JSON.parse(configurationString); - } catch (e) { - const now = eat.now(); - this.file.fail(`Unable to parse lens JSON configuration: ${e}`, { - line: now.line, - column: now.column + 6, - }); - } - } - - match += '}'; - - return eat(match)({ - type: 'lens', - ...configuration, - }); - }; - - tokenizers.lens = tokenizeLens; - methods.splice(methods.indexOf('text'), 0, 'lens'); -}; diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx deleted file mode 100644 index bd0b22abc7acd..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/lens/plugin.tsx +++ /dev/null @@ -1,388 +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. - */ - -import { first } from 'rxjs/operators'; -import { - EuiFieldText, - EuiModalBody, - EuiModalHeader, - EuiModalHeaderTitle, - EuiMarkdownEditorUiPlugin, - EuiMarkdownContext, - EuiCodeBlock, - EuiSpacer, - EuiModalFooter, - EuiButtonEmpty, - EuiButton, - EuiFlexItem, - EuiFlexGroup, - EuiFormRow, - EuiMarkdownAstNodePosition, -} from '@elastic/eui'; -import React, { useCallback, useContext, useMemo, useEffect, useState } from 'react'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import moment from 'moment'; -import { useLocation } from 'react-router-dom'; - -import type { TypedLensByValueInput } from '../../../../../../../lens/public'; -import { useKibana } from '../../../../lib/kibana'; -import { LensMarkDownRenderer } from './processor'; -import { DRAFT_COMMENT_STORAGE_ID, ID } from './constants'; -import { CommentEditorContext } from './context'; -import { LensSavedObjectsModal } from './lens_saved_objects_modal'; -import { ModalContainer } from './modal_container'; -import { getLensAttributes } from './helpers'; -import type { - EmbeddablePackageState, - EmbeddableInput, -} from '../../../../../../../../../src/plugins/embeddable/public'; - -type LensIncomingEmbeddablePackage = Omit & { - input: Omit & { - id: string | undefined; - attributes: TypedLensByValueInput['attributes']; - }; -}; - -type LensEuiMarkdownEditorUiPlugin = EuiMarkdownEditorUiPlugin<{ - title: string; - startDate: string; - endDate: string; - position: EuiMarkdownAstNodePosition; - attributes: TypedLensByValueInput['attributes']; -}>; - -const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ - node, - onCancel, - onSave, -}) => { - const location = useLocation(); - const { - application: { currentAppId$ }, - embeddable, - lens, - storage, - data: { - indexPatterns, - query: { - timefilter: { timefilter }, - }, - }, - } = useKibana().services; - - const [currentAppId, setCurrentAppId] = useState(undefined); - - const [editMode, setEditMode] = useState(!!node); - const [lensEmbeddableAttributes, setLensEmbeddableAttributes] = useState( - node?.attributes ?? null - ); - const [startDate, setStartDate] = useState( - node?.startDate ? moment(node.startDate).format() : 'now-7d' - ); - const [endDate, setEndDate] = useState( - node?.endDate ? moment(node.endDate).format() : 'now' - ); - const [lensTitle, setLensTitle] = useState(node?.title ?? ''); - const [showLensSavedObjectsModal, setShowLensSavedObjectsModal] = useState(false); - const commentEditorContext = useContext(CommentEditorContext); - const markdownContext = useContext(EuiMarkdownContext); - - const handleTitleChange = useCallback((e) => { - setLensTitle(e.target.value); - }, []); - - const handleAdd = useCallback(() => { - let draftComment; - if (storage.get(DRAFT_COMMENT_STORAGE_ID)) { - try { - draftComment = JSON.parse(storage.get(DRAFT_COMMENT_STORAGE_ID)); - // eslint-disable-next-line no-empty - } catch (e) {} - } - - if (currentAppId) { - // clears Lens incoming state - embeddable?.getStateTransfer().getIncomingEmbeddablePackage(currentAppId, true); - } - - if (!node && draftComment.position) { - markdownContext.replaceNode( - draftComment.position, - `!{${ID}${JSON.stringify({ - startDate, - endDate, - title: lensTitle, - attributes: lensEmbeddableAttributes, - })}}` - ); - - onCancel(); - return; - } - - if (lensEmbeddableAttributes) { - onSave( - `!{${ID}${JSON.stringify({ - startDate, - endDate, - title: lensTitle, - attributes: lensEmbeddableAttributes, - })}}`, - { - block: true, - } - ); - } - }, [ - storage, - currentAppId, - node, - lensEmbeddableAttributes, - embeddable, - markdownContext, - startDate, - endDate, - lensTitle, - onCancel, - onSave, - ]); - - const originatingPath = useMemo(() => `${location.pathname}${location.search}`, [ - location.pathname, - location.search, - ]); - - const handleEditInLensClick = useCallback(async () => { - storage.set( - DRAFT_COMMENT_STORAGE_ID, - JSON.stringify({ - commentId: commentEditorContext?.editorId, - comment: commentEditorContext?.value, - position: node?.position, - title: lensTitle, - }) - ); - - lens?.navigateToPrefilledEditor( - { - id: '', - timeRange: { - from: (lensEmbeddableAttributes && startDate) ?? 'now-7d', - to: (lensEmbeddableAttributes && endDate) ?? 'now', - mode: lensEmbeddableAttributes ? 'absolute' : 'relative', - }, - attributes: lensEmbeddableAttributes ?? getLensAttributes(await indexPatterns.getDefault()), - }, - { - originatingApp: currentAppId!, - originatingPath, - } - ); - }, [ - storage, - commentEditorContext?.editorId, - commentEditorContext?.value, - node?.position, - lensTitle, - lens, - lensEmbeddableAttributes, - startDate, - endDate, - indexPatterns, - currentAppId, - originatingPath, - ]); - - const handleChooseLensSO = useCallback( - (savedObjectId, savedObjectType, fullName, savedObject) => { - setLensEmbeddableAttributes({ - ...savedObject.attributes, - title: '', - references: savedObject.references, - }); - setShowLensSavedObjectsModal(false); - }, - [] - ); - - const handleCloseLensSOModal = useCallback(() => setShowLensSavedObjectsModal(false), []); - - useEffect(() => { - const getCurrentAppId = async () => { - const appId = await currentAppId$.pipe(first()).toPromise(); - setCurrentAppId(appId); - }; - getCurrentAppId(); - }, [currentAppId$]); - - useEffect(() => { - let incomingEmbeddablePackage; - - if (currentAppId) { - incomingEmbeddablePackage = embeddable - ?.getStateTransfer() - .getIncomingEmbeddablePackage(currentAppId) as LensIncomingEmbeddablePackage; - } - - if ( - incomingEmbeddablePackage?.type === 'lens' && - incomingEmbeddablePackage?.input?.attributes - ) { - setLensEmbeddableAttributes(incomingEmbeddablePackage?.input.attributes); - const lensTime = timefilter.getTime(); - if (lensTime?.from && lensTime?.to) { - setStartDate(lensTime.from); - setEndDate(lensTime.to); - } - } - - let draftComment; - if (storage.get(DRAFT_COMMENT_STORAGE_ID)) { - try { - draftComment = JSON.parse(storage.get(DRAFT_COMMENT_STORAGE_ID)); - if (draftComment.title) { - setLensTitle(draftComment.title); - } - setEditMode(true); - // eslint-disable-next-line no-empty - } catch (e) {} - } - }, [embeddable, storage, timefilter, currentAppId]); - - return ( - <> - - - - {editMode ? ( - - ) : ( - - )} - - - - - - - - - - - setShowLensSavedObjectsModal(true)} - > - - - - - {lensEmbeddableAttributes ? ( - - - - - - - - - - - - - ) : null} - - - - - {'Cancel'} - - {editMode ? ( - - ) : ( - - )} - - - - {showLensSavedObjectsModal ? ( - - ) : null} - - ); -}; - -export const LensEditor = React.memo(LensEditorComponent); - -export const plugin: LensEuiMarkdownEditorUiPlugin = { - name: ID, - button: { - label: i18n.translate( - 'xpack.securitySolution.markdownEditor.plugins.lens.insertLensButtonLabel', - { - defaultMessage: 'Insert lens visualization', - } - ), - iconType: 'lensApp', - }, - helpText: ( - - {'!{lens}'} - - ), - editor: LensEditor, -}; From 27c12f381b659b3fb8ea91a119cb0fcf301c3617 Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Tue, 29 Jun 2021 21:47:02 +0200 Subject: [PATCH 20/41] PR comments --- .../cases/public/components/create/index.tsx | 5 -- .../plugins/lens/lens_saved_objects_modal.tsx | 8 +-- .../markdown_editor/plugins/lens/plugin.tsx | 18 +++--- .../components/user_action_tree/index.tsx | 32 +++++----- .../user_action_tree/translations.ts | 2 +- .../user_action_tree/user_action_markdown.tsx | 60 ++++++++----------- 6 files changed, 56 insertions(+), 69 deletions(-) diff --git a/x-pack/plugins/cases/public/components/create/index.tsx b/x-pack/plugins/cases/public/components/create/index.tsx index cc2fac856092a..7f8b8f664529e 100644 --- a/x-pack/plugins/cases/public/components/create/index.tsx +++ b/x-pack/plugins/cases/public/components/create/index.tsx @@ -17,7 +17,6 @@ import { SubmitCaseButton } from './submit_button'; import { Case } from '../../containers/types'; import { CaseType } from '../../../common'; import { CasesTimelineIntegration, CasesTimelineIntegrationProvider } from '../timeline_context'; -// import { CasesLensIntegration, CasesLensIntegrationProvider } from '../lens_context'; import { fieldName as descriptionFieldName } from './description'; import { InsertTimeline } from '../insert_timeline'; import { UsePostComment } from '../../containers/use_post_comment'; @@ -39,7 +38,6 @@ export interface CreateCaseProps extends Owner { hideConnectorServiceNowSir?: boolean; onCancel: () => void; onSuccess: (theCase: Case) => Promise; - // lensIntegration?: CasesLensIntegration; timelineIntegration?: CasesTimelineIntegration; withSteps?: boolean; } @@ -51,12 +49,10 @@ const CreateCaseComponent = ({ disableAlerts, onCancel, onSuccess, - // lensIntegration, timelineIntegration, withSteps, }: Omit) => ( - {/* */} - {/* */} ); diff --git a/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx index 56f57ef284098..1fa23c01d68f5 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx @@ -40,7 +40,7 @@ const LensSavedObjectsModalComponent: React.FC = ({ type: 'lens', getIconForSavedObject: () => 'lensApp', name: i18n.translate( - 'xpack.observability.markdownEditor.plugins.lens.insertLensSavedObjectModal.searchSelection.savedObjectType.lens', + 'xpack.cases.markdownEditor.plugins.lens.insertLensSavedObjectModal.searchSelection.savedObjectType.lens', { defaultMessage: 'Lens', } @@ -58,8 +58,8 @@ const LensSavedObjectsModalComponent: React.FC = ({

@@ -72,7 +72,7 @@ const LensSavedObjectsModalComponent: React.FC = ({ showFilter noItemsMessage={ } diff --git a/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx index 8ca0323a89642..9c00793617be5 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx @@ -262,12 +262,12 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ {editMode ? ( ) : ( )} @@ -284,7 +284,7 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ fill > @@ -297,7 +297,7 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ onClick={() => setShowLensSavedObjectsModal(true)} > @@ -309,7 +309,7 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ @@ -347,12 +347,12 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ {editMode ? ( ) : ( )} @@ -371,7 +371,7 @@ export const LensEditor = React.memo(LensEditorComponent); export const plugin: LensEuiMarkdownEditorUiPlugin = { name: ID, button: { - label: i18n.translate('xpack.observability.markdownEditor.plugins.lens.insertLensButtonLabel', { + label: i18n.translate('xpack.cases.markdownEditor.plugins.lens.insertLensButtonLabel', { defaultMessage: 'Insert visualization', }), iconType: 'lensApp', diff --git a/x-pack/plugins/cases/public/components/user_action_tree/index.tsx b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx index e61e776ca5d16..ad175c82e2350 100644 --- a/x-pack/plugins/cases/public/components/user_action_tree/index.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx @@ -160,16 +160,13 @@ export const UserActionTree = React.memo( getManualAlertIdsWithNoRuleId(caseData.comments) ); - const handleManageMarkdownEditId = useCallback( - (id: string) => { - if (!manageMarkdownEditIds.includes(id)) { - setManageMarkdownEditIds([...manageMarkdownEditIds, id]); - } else { - setManageMarkdownEditIds(manageMarkdownEditIds.filter((myId) => id !== myId)); - } - }, - [manageMarkdownEditIds] - ); + const handleManageMarkdownEditId = useCallback((id: string) => { + setManageMarkdownEditIds((prevManageMarkdownEditIds) => + !prevManageMarkdownEditIds.includes(id) + ? prevManageMarkdownEditIds.concat(id) + : prevManageMarkdownEditIds.filter((myId) => id !== myId) + ); + }, []); const handleSaveComment = useCallback( ({ id, version }: { id: string; version: string }, content: string) => { @@ -627,12 +624,15 @@ export const UserActionTree = React.memo( } if (draftComment?.commentId) { - if ( - ![NEW_ID, DESCRIPTION_ID].includes(draftComment.commentId) && - !manageMarkdownEditIds.includes(draftComment.commentId) - ) { - setManageMarkdownEditIds([draftComment.commentId]); - } + setManageMarkdownEditIds((prevManageMarkdownEditIds) => { + if ( + ![NEW_ID, DESCRIPTION_ID].includes(draftComment.commentId) && + !prevManageMarkdownEditIds.includes(draftComment.commentId) + ) { + return [draftComment.commentId]; + } + return prevManageMarkdownEditIds; + }); if ( commentRefs.current && diff --git a/x-pack/plugins/cases/public/components/user_action_tree/translations.ts b/x-pack/plugins/cases/public/components/user_action_tree/translations.ts index f9c1a878f9829..aa7e6e7f084a8 100644 --- a/x-pack/plugins/cases/public/components/user_action_tree/translations.ts +++ b/x-pack/plugins/cases/public/components/user_action_tree/translations.ts @@ -60,6 +60,6 @@ export const UNKNOWN_RULE = i18n.translate('xpack.cases.caseView.unknownRule.lab export const INSERT_LENS = i18n.translate( 'xpack.cases.markdownEditor.plugins.lens.insertLensButtonLabel', { - defaultMessage: 'Insert lens visualization', + defaultMessage: 'Insert visualization', } ); diff --git a/x-pack/plugins/cases/public/components/user_action_tree/user_action_markdown.tsx b/x-pack/plugins/cases/public/components/user_action_tree/user_action_markdown.tsx index 97a1d39d096ba..df4861ef7ed7e 100644 --- a/x-pack/plugins/cases/public/components/user_action_tree/user_action_markdown.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/user_action_markdown.tsx @@ -67,36 +67,6 @@ export const UserActionMarkdown = forwardRef ( - - - - {i18n.CANCEL} - - - - - {i18n.SAVE} - - - - ), - [] - ); - return isEditable ? (
+ + + {i18n.CANCEL} + + + + + {i18n.SAVE} + + + + ), }} /> From 6688536e1137a2b97abf044296a2653aa63427b6 Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Wed, 14 Jul 2021 17:29:55 +0200 Subject: [PATCH 21/41] update types --- src/plugins/embeddable/public/public.api.md | 2 +- .../common/lib/kibana/__mocks__/index.ts | 6 +- .../public/common/lib/kibana/services.ts | 2 +- .../components/markdown_editor/editor.tsx | 18 +- .../markdown_editor/plugins/lens/plugin.tsx | 209 ++++++++++-------- .../plugins/lens/processor.tsx | 40 ++-- .../components/user_action_tree/index.tsx | 57 ++--- 7 files changed, 181 insertions(+), 153 deletions(-) diff --git a/src/plugins/embeddable/public/public.api.md b/src/plugins/embeddable/public/public.api.md index 1efcc8659bcd5..6a33f561cd8e7 100644 --- a/src/plugins/embeddable/public/public.api.md +++ b/src/plugins/embeddable/public/public.api.md @@ -362,9 +362,9 @@ export interface EmbeddableEditorState { embeddableId?: string; // (undocumented) originatingApp: string; - searchSessionId?: string; // (undocumented) originatingPath?: string; + searchSessionId?: string; // (undocumented) valueInput?: EmbeddableInput; } diff --git a/x-pack/plugins/cases/public/common/lib/kibana/__mocks__/index.ts b/x-pack/plugins/cases/public/common/lib/kibana/__mocks__/index.ts index 392b71befe2b4..fb5e3f89d74b1 100644 --- a/x-pack/plugins/cases/public/common/lib/kibana/__mocks__/index.ts +++ b/x-pack/plugins/cases/public/common/lib/kibana/__mocks__/index.ts @@ -12,7 +12,11 @@ import { createWithKibanaMock, } from '../kibana_react.mock'; -export const KibanaServices = { get: jest.fn(), getKibanaVersion: jest.fn(() => '8.0.0') }; +export const KibanaServices = { + get: jest.fn(), + getKibanaVersion: jest.fn(() => '8.0.0'), + getConfig: jest.fn(() => null), +}; export const useKibana = jest.fn().mockReturnValue({ services: createStartServicesMock(), }); diff --git a/x-pack/plugins/cases/public/common/lib/kibana/services.ts b/x-pack/plugins/cases/public/common/lib/kibana/services.ts index e3a75d468ad02..3a1f220d9794f 100644 --- a/x-pack/plugins/cases/public/common/lib/kibana/services.ts +++ b/x-pack/plugins/cases/public/common/lib/kibana/services.ts @@ -19,7 +19,7 @@ export class KibanaServices { http, kibanaVersion, config, - }: GlobalServices & { kibanaVersion: string } & { config: any }) { + }: GlobalServices & { kibanaVersion: string; config: CasesUiConfigType }) { this.services = { http }; this.kibanaVersion = kibanaVersion; this.config = config; diff --git a/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx b/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx index 6c015c1ce219c..72f6e1b7eb24d 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx @@ -9,6 +9,7 @@ import React, { memo, forwardRef, useCallback, + useMemo, useRef, useState, useImperativeHandle, @@ -49,7 +50,15 @@ const MarkdownEditorComponent = forwardRef(null); - // @ts-expect-error update types + const commentEditorContextValue = useMemo( + () => ({ + editorId, + value, + }), + [editorId, value] + ); + + // @ts-expect-error useImperativeHandle(ref, () => { if (!editorRef.current) { return null; @@ -81,12 +90,7 @@ const MarkdownEditorComponent = forwardRef + {editor} ); diff --git a/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx index 9c00793617be5..8e395faf6d801 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx @@ -26,7 +26,6 @@ import { import React, { useCallback, useContext, useMemo, useEffect, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import moment from 'moment'; import { useLocation } from 'react-router-dom'; import type { TypedLensByValueInput } from '../../../../../../lens/public'; @@ -51,6 +50,7 @@ type LensIncomingEmbeddablePackage = Omit & { type LensEuiMarkdownEditorUiPlugin = EuiMarkdownEditorUiPlugin<{ title: string; + timeRange: TypedLensByValueInput['timeRange']; startDate: string; endDate: string; position: EuiMarkdownAstNodePosition; @@ -79,22 +79,29 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ const [currentAppId, setCurrentAppId] = useState(undefined); const [editMode, setEditMode] = useState(!!node); - const [lensEmbeddableAttributes, setLensEmbeddableAttributes] = useState( - node?.attributes ?? null - ); - const [startDate, setStartDate] = useState( - node?.startDate ? moment(node.startDate).format() : 'now-7d' - ); - const [endDate, setEndDate] = useState( - node?.endDate ? moment(node.endDate).format() : 'now' + const [lensEmbeddableAttributes, setLensEmbeddableAttributes] = useState< + TypedLensByValueInput['attributes'] | null + >(null); + const [timeRange, setTimeRange] = useState( + node?.timeRange ?? { + from: 'now-7d', + to: 'now', + mode: 'relative', + } ); - const [lensTitle, setLensTitle] = useState(node?.title ?? ''); const [showLensSavedObjectsModal, setShowLensSavedObjectsModal] = useState(false); const commentEditorContext = useContext(CommentEditorContext); const markdownContext = useContext(EuiMarkdownContext); const handleTitleChange = useCallback((e) => { - setLensTitle(e.target.value); + const title = e.target.value ?? ''; + setLensEmbeddableAttributes((currentValue) => { + if (currentValue) { + return { ...currentValue, title } as TypedLensByValueInput['attributes']; + } + + return currentValue; + }); }, []); const handleAdd = useCallback(() => { @@ -106,19 +113,13 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ } catch (e) {} } - if (currentAppId) { - // clears Lens incoming state - embeddable?.getStateTransfer().getIncomingEmbeddablePackage(currentAppId, true); - } - - if (!node && draftComment.position) { + if (!node && draftComment?.position) { markdownContext.replaceNode( draftComment.position, `!{${ID}${JSON.stringify({ - startDate, - endDate, - title: lensTitle, + timeRange, attributes: lensEmbeddableAttributes, + editMode, })}}` ); @@ -129,10 +130,9 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ if (lensEmbeddableAttributes) { onSave( `!{${ID}${JSON.stringify({ - startDate, - endDate, - title: lensTitle, + timeRange, attributes: lensEmbeddableAttributes, + editMode, })}}`, { block: true, @@ -141,78 +141,98 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ } }, [ storage, - currentAppId, node, lensEmbeddableAttributes, - embeddable, markdownContext, - startDate, - endDate, - lensTitle, + timeRange, + editMode, onCancel, onSave, ]); + const handleDelete = useCallback(() => { + if (node?.position) { + markdownContext.replaceNode(node.position, ``); + onCancel(); + return; + } + + let draftComment; + if (storage.get(DRAFT_COMMENT_STORAGE_ID)) { + try { + draftComment = JSON.parse(storage.get(DRAFT_COMMENT_STORAGE_ID)); + markdownContext.replaceNode(draftComment?.position, ``); + onCancel(); + + // eslint-disable-next-line no-empty + } catch (e) {} + } + }, [markdownContext, node?.position, onCancel, storage]); + const originatingPath = useMemo(() => `${location.pathname}${location.search}`, [ location.pathname, location.search, ]); - const handleEditInLensClick = useCallback(async () => { - storage.set( - DRAFT_COMMENT_STORAGE_ID, - JSON.stringify({ - commentId: commentEditorContext?.editorId, - comment: commentEditorContext?.value, - position: node?.position, - title: lensTitle, - }) - ); + const handleEditInLensClick = useCallback( + async (lensAttributes?) => { + storage.set( + DRAFT_COMMENT_STORAGE_ID, + JSON.stringify({ + commentId: commentEditorContext?.editorId, + comment: commentEditorContext?.value, + position: node?.position, + }) + ); - lens?.navigateToPrefilledEditor( - { - id: '', - timeRange: { - from: (lensEmbeddableAttributes && startDate) ?? 'now-7d', - to: (lensEmbeddableAttributes && endDate) ?? 'now', - mode: lensEmbeddableAttributes ? 'absolute' : 'relative', + lens?.navigateToPrefilledEditor( + { + id: '', + timeRange, + attributes: + lensAttributes ?? + lensEmbeddableAttributes ?? + getLensAttributes(await indexPatterns.getDefault()), }, - attributes: lensEmbeddableAttributes ?? getLensAttributes(await indexPatterns.getDefault()), - }, - { - originatingApp: currentAppId!, - originatingPath, - } - ); - }, [ - storage, - commentEditorContext?.editorId, - commentEditorContext?.value, - node?.position, - lensTitle, - lens, - lensEmbeddableAttributes, - startDate, - endDate, - indexPatterns, - currentAppId, - originatingPath, - ]); + { + originatingApp: currentAppId!, + originatingPath, + } + ); + }, + [ + storage, + commentEditorContext?.editorId, + commentEditorContext?.value, + node?.position, + lensEmbeddableAttributes, + lens, + timeRange, + indexPatterns, + currentAppId, + originatingPath, + ] + ); const handleChooseLensSO = useCallback( (savedObjectId, savedObjectType, fullName, savedObject) => { - setLensEmbeddableAttributes({ + handleEditInLensClick({ ...savedObject.attributes, title: '', references: savedObject.references, }); - setShowLensSavedObjectsModal(false); }, - [] + [handleEditInLensClick] ); const handleCloseLensSOModal = useCallback(() => setShowLensSavedObjectsModal(false), []); + useEffect(() => { + if (node?.attributes) { + setLensEmbeddableAttributes(node.attributes); + } + }, [node?.attributes]); + useEffect(() => { const getCurrentAppId = async () => { const appId = await currentAppId$.pipe(first()).toPromise(); @@ -227,7 +247,7 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ if (currentAppId) { incomingEmbeddablePackage = embeddable ?.getStateTransfer() - .getIncomingEmbeddablePackage(currentAppId) as LensIncomingEmbeddablePackage; + .getIncomingEmbeddablePackage(currentAppId, true) as LensIncomingEmbeddablePackage; } if ( @@ -237,21 +257,21 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ setLensEmbeddableAttributes(incomingEmbeddablePackage?.input.attributes); const lensTime = timefilter.getTime(); if (lensTime?.from && lensTime?.to) { - setStartDate(lensTime.from); - setEndDate(lensTime.to); + setTimeRange({ + from: lensTime.from, + to: lensTime.to, + mode: [lensTime.from, lensTime.to].join('').includes('now') ? 'relative' : 'absolute', + }); } - } - let draftComment; - if (storage.get(DRAFT_COMMENT_STORAGE_ID)) { - try { - draftComment = JSON.parse(storage.get(DRAFT_COMMENT_STORAGE_ID)); - if (draftComment.title) { - setLensTitle(draftComment.title); - } - setEditMode(true); - // eslint-disable-next-line no-empty - } catch (e) {} + let draftComment; + if (storage.get(DRAFT_COMMENT_STORAGE_ID)) { + try { + draftComment = JSON.parse(storage.get(DRAFT_COMMENT_STORAGE_ID)); + setEditMode(!!draftComment?.editMode); + // eslint-disable-next-line no-empty + } catch (e) {} + } } }, [embeddable, storage, timefilter, currentAppId]); @@ -277,7 +297,7 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ handleEditInLensClick()} color="primary" size="m" iconType="lensApp" @@ -314,7 +334,7 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ defaultMessage: 'Visualization title', } )} - value={lensTitle} + value={lensEmbeddableAttributes?.title ?? ''} onChange={handleTitleChange} />
@@ -324,7 +344,7 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ iconType="lensApp" fullWidth={false} isDisabled={!lens?.canUseEditor() || lensEmbeddableAttributes === null} - onClick={handleEditInLensClick} + onClick={() => handleEditInLensClick()} > {'Cancel'} - + {editMode ? ( + + + + ) : null} + {editMode ? ( = ({ attributes, - title, - startDate, - endDate, + timeRange, viewMode = true, }) => { const location = useLocation(); @@ -64,11 +62,7 @@ const LensMarkDownRendererComponent: React.FC = ({ navigateToPrefilledEditor( { id: '', - timeRange: { - from: startDate ?? 'now-7d', - to: endDate ?? 'now', - mode: startDate ? 'absolute' : 'relative', - }, + timeRange, attributes, }, options @@ -77,11 +71,10 @@ const LensMarkDownRendererComponent: React.FC = ({ }, [ attributes, currentAppId, - endDate, location.pathname, location.search, navigateToPrefilledEditor, - startDate, + timeRange, viewMode, ]); @@ -100,15 +93,15 @@ const LensMarkDownRendererComponent: React.FC = ({ -
{title}
+
{attributes.title}
- {viewMode ? ( + {viewMode && canUseEditor() ? ( {`Open visualization`} @@ -119,18 +112,13 @@ const LensMarkDownRendererComponent: React.FC = ({ - {attributes ? ( - - ) : null} + ) : null} diff --git a/x-pack/plugins/cases/public/components/user_action_tree/index.tsx b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx index b846a6aa86cf9..cf2b4924cfc2e 100644 --- a/x-pack/plugins/cases/public/components/user_action_tree/index.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx @@ -650,39 +650,40 @@ export const UserActionTree = React.memo( .getIncomingEmbeddablePackage(currentAppId); if (incomingEmbeddablePackage) { - let draftComment: DraftComment | undefined; + let draftComment: DraftComment; if (storage.get(DRAFT_COMMENT_STORAGE_ID)) { try { draftComment = JSON.parse(storage.get(DRAFT_COMMENT_STORAGE_ID)); - // eslint-disable-next-line no-empty - } catch (e) {} - } - - if (draftComment && draftComment?.commentId) { - setManageMarkdownEditIds((prevManageMarkdownEditIds) => { - if ( - ![NEW_ID, DESCRIPTION_ID].includes(draftComment.commentId) && - !prevManageMarkdownEditIds.includes(draftComment.commentId) - ) { - return [draftComment.commentId]; - } - return prevManageMarkdownEditIds; - }); - if ( - commentRefs.current && - commentRefs.current[draftComment.commentId] && - commentRefs.current[draftComment.commentId].editor?.textarea && - commentRefs.current[draftComment.commentId].editor?.toolbar - ) { - commentRefs.current[draftComment.commentId].setComment(draftComment.comment); - const lensPluginButton = commentRefs.current[ - draftComment.commentId - ].editor?.toolbar?.querySelector(`[aria-label="${i18n.INSERT_LENS}"]`); - if (lensPluginButton) { - lensPluginButton.click(); + if (draftComment?.commentId) { + setManageMarkdownEditIds((prevManageMarkdownEditIds) => { + if ( + ![NEW_ID, DESCRIPTION_ID].includes(draftComment?.commentId) && + !prevManageMarkdownEditIds.includes(draftComment?.commentId) + ) { + return [draftComment?.commentId]; + } + return prevManageMarkdownEditIds; + }); + + if ( + commentRefs.current && + commentRefs.current[draftComment.commentId] && + commentRefs.current[draftComment.commentId].editor?.textarea && + commentRefs.current[draftComment.commentId].editor?.toolbar + ) { + commentRefs.current[draftComment.commentId].setComment(draftComment.comment); + const lensPluginButton = commentRefs.current[ + draftComment.commentId + ].editor?.toolbar?.querySelector(`[aria-label="${i18n.INSERT_LENS}"]`); + if (lensPluginButton) { + lensPluginButton.click(); + storage.remove(DRAFT_COMMENT_STORAGE_ID); + } + } } - } + // eslint-disable-next-line no-empty + } catch (e) {} } } }; From e4310b097c039d90a6e3587397ca099fffdfa37f Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Sat, 17 Jul 2021 11:20:10 +0200 Subject: [PATCH 22/41] cleanup --- .../components/markdown_editor/editor.tsx | 32 +++-- .../markdown_editor/plugins/lens/plugin.tsx | 4 +- .../components/markdown_editor/use_plugins.ts | 9 +- .../user_action_tree/user_action_markdown.tsx | 59 ++++----- x-pack/plugins/cases/public/types.ts | 2 +- .../cases/server/common/lens_parser.ts | 79 ++++++++++++ .../server/common/models/commentable_case.ts | 30 ++++- .../plugins/cases/server/common/utils.test.ts | 114 +++++++++++++++++- x-pack/plugins/cases/server/common/utils.ts | 67 +++++++++- .../server/services/attachments/index.ts | 17 ++- x-pack/plugins/lens/public/plugin.ts | 15 ++- x-pack/plugins/lens/server/index.ts | 1 + 12 files changed, 358 insertions(+), 71 deletions(-) create mode 100644 x-pack/plugins/cases/server/common/lens_parser.ts diff --git a/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx b/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx index 72f6e1b7eb24d..64aac233f1bb9 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx @@ -72,26 +72,22 @@ const MarkdownEditorComponent = forwardRef - ); - return ( - {editor} + ); } diff --git a/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx index 8e395faf6d801..7985962b92251 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx @@ -118,8 +118,8 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ draftComment.position, `!{${ID}${JSON.stringify({ timeRange, - attributes: lensEmbeddableAttributes, editMode, + attributes: lensEmbeddableAttributes, })}}` ); @@ -131,8 +131,8 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ onSave( `!{${ID}${JSON.stringify({ timeRange, - attributes: lensEmbeddableAttributes, editMode, + attributes: lensEmbeddableAttributes, })}}`, { block: true, diff --git a/x-pack/plugins/cases/public/components/markdown_editor/use_plugins.ts b/x-pack/plugins/cases/public/components/markdown_editor/use_plugins.ts index 4249a014339bd..b87b9ae6ad09a 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/use_plugins.ts +++ b/x-pack/plugins/cases/public/components/markdown_editor/use_plugins.ts @@ -36,13 +36,12 @@ export const usePlugins = () => { if (kibanaConfig?.markdownPlugins?.lens) { uiPlugins.push(lensMarkdownPlugin.plugin); - - parsingPlugins.push(lensMarkdownPlugin.parser); - - // This line of code is TS-compatible and it will break if [1][1] change in the future. - processingPlugins[1][1].components.lens = lensMarkdownPlugin.renderer; } + parsingPlugins.push(lensMarkdownPlugin.parser); + // This line of code is TS-compatible and it will break if [1][1] change in the future. + processingPlugins[1][1].components.lens = lensMarkdownPlugin.renderer; + return { uiPlugins, parsingPlugins, diff --git a/x-pack/plugins/cases/public/components/user_action_tree/user_action_markdown.tsx b/x-pack/plugins/cases/public/components/user_action_tree/user_action_markdown.tsx index df4861ef7ed7e..eaa522c3abcf1 100644 --- a/x-pack/plugins/cases/public/components/user_action_tree/user_action_markdown.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/user_action_markdown.tsx @@ -6,7 +6,7 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty, EuiButton } from '@elastic/eui'; -import React, { forwardRef, useCallback, useImperativeHandle, useRef } from 'react'; +import React, { forwardRef, useCallback, useImperativeHandle, useMemo, useRef } from 'react'; import styled from 'styled-components'; import * as i18n from '../case_view/translations'; @@ -62,6 +62,36 @@ export const UserActionMarkdown = forwardRef ( + + + + {i18n.CANCEL} + + + + + {i18n.SAVE} + + + + ), + [handleCancelAction, handleSaveAction] + ); + useImperativeHandle(ref, () => ({ setComment, editor: editorRef.current, @@ -77,32 +107,7 @@ export const UserActionMarkdown = forwardRef - - - {i18n.CANCEL} - - - - - {i18n.SAVE} - - -
- ), + bottomRightContent: EditorButtons, }} /> diff --git a/x-pack/plugins/cases/public/types.ts b/x-pack/plugins/cases/public/types.ts index 70363aa836e00..db2e5d6ab6bff 100644 --- a/x-pack/plugins/cases/public/types.ts +++ b/x-pack/plugins/cases/public/types.ts @@ -14,7 +14,7 @@ import type { TriggersAndActionsUIPublicPluginSetup as TriggersActionsSetup, TriggersAndActionsUIPublicPluginStart as TriggersActionsStart, } from '../../triggers_actions_ui/public'; -import { DataPublicPluginStart } from '../../../../src/plugins/data/public'; +import type { DataPublicPluginStart } from '../../../../src/plugins/data/public'; import type { EmbeddableStart } from '../../../../src/plugins/embeddable/public'; import type { Storage } from '../../../../src/plugins/kibana_utils/public'; diff --git a/x-pack/plugins/cases/server/common/lens_parser.ts b/x-pack/plugins/cases/server/common/lens_parser.ts new file mode 100644 index 0000000000000..4846ad2bcb9de --- /dev/null +++ b/x-pack/plugins/cases/server/common/lens_parser.ts @@ -0,0 +1,79 @@ +/* + * 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. + */ + +import { Plugin } from 'unified'; +import { RemarkTokenizer } from '@elastic/eui'; +// import { ID } from './constants'; + +export const ID = 'lens'; + +export const LensParser: Plugin = function () { + const Parser = this.Parser; + const tokenizers = Parser.prototype.blockTokenizers; + const methods = Parser.prototype.blockMethods; + + const tokenizeLens: RemarkTokenizer = function (eat, value, silent) { + if (value.startsWith(`!{${ID}`) === false) return true; + + const nextChar = value[6]; + + if (nextChar !== '{' && nextChar !== '}') return false; // this isn't actually a lens + + if (silent) { + return true; + } + + // is there a configuration? + const hasConfiguration = nextChar === '{'; + + let match = `!{${ID}`; + let configuration = {}; + + if (hasConfiguration) { + let configurationString = ''; + + let openObjects = 0; + + for (let i = 6; i < value.length; i++) { + const char = value[i]; + if (char === '{') { + openObjects++; + configurationString += char; + } else if (char === '}') { + openObjects--; + if (openObjects === -1) { + break; + } + configurationString += char; + } else { + configurationString += char; + } + } + + match += configurationString; + try { + configuration = JSON.parse(configurationString); + } catch (e) { + const now = eat.now(); + this.file.fail(`Unable to parse lens JSON configuration: ${e}`, { + line: now.line, + column: now.column + 6, + }); + } + } + + match += '}'; + + return eat(match)({ + type: ID, + ...configuration, + }); + }; + + tokenizers.lens = tokenizeLens; + methods.splice(methods.indexOf('text'), 0, ID); +}; diff --git a/x-pack/plugins/cases/server/common/models/commentable_case.ts b/x-pack/plugins/cases/server/common/models/commentable_case.ts index e082a0b290f16..b8f97ddc2b6f1 100644 --- a/x-pack/plugins/cases/server/common/models/commentable_case.ts +++ b/x-pack/plugins/cases/server/common/models/commentable_case.ts @@ -10,6 +10,7 @@ import { SavedObject, SavedObjectReference, SavedObjectsClientContract, + SavedObjectsUpdateOptions, SavedObjectsUpdateResponse, Logger, } from 'src/core/server'; @@ -30,6 +31,7 @@ import { SUB_CASE_SAVED_OBJECT, SubCaseAttributes, User, + CommentRequestUserType, } from '../../../common'; import { transformESConnectorToCaseConnector, @@ -40,6 +42,7 @@ import { import { AttachmentService, CasesService } from '../../services'; import { createCaseError } from '../error'; import { countAlertsForID } from '../index'; +import { getOrUpdateLensReferences } from '../utils'; interface UpdateCommentResp { comment: SavedObjectsUpdateResponse; @@ -216,6 +219,22 @@ export class CommentableCase { }): Promise { try { const { id, version, ...queryRestAttributes } = updateRequest; + const options: SavedObjectsUpdateOptions = { + version, + }; + + if (queryRestAttributes.type === CommentType.user && queryRestAttributes?.comment) { + const currentComment = (await this.attachmentService.get({ + unsecuredSavedObjectsClient: this.unsecuredSavedObjectsClient, + attachmentId: id, + })) as SavedObject; + + const updatedReferences = getOrUpdateLensReferences( + queryRestAttributes.comment, + currentComment + ); + options.references = updatedReferences; + } const [comment, commentableCase] = await Promise.all([ this.attachmentService.update({ @@ -226,7 +245,7 @@ export class CommentableCase { updated_at: updatedAt, updated_by: user, }, - version, + options, }), this.update({ date: updatedAt, user }), ]); @@ -272,6 +291,13 @@ export class CommentableCase { throw Boom.badRequest('The owner field of the comment must match the case'); } + const references = this.buildRefsToCase(); + + if (commentReq.type === CommentType.user && commentReq?.comment) { + const commentStringReferences = getOrUpdateLensReferences(commentReq.comment); + references.concat(commentStringReferences); + } + const [comment, commentableCase] = await Promise.all([ this.attachmentService.create({ unsecuredSavedObjectsClient: this.unsecuredSavedObjectsClient, @@ -281,7 +307,7 @@ export class CommentableCase { ...commentReq, ...user, }), - references: this.buildRefsToCase(), + references, id, }), this.update({ date: createdDate, user }), diff --git a/x-pack/plugins/cases/server/common/utils.test.ts b/x-pack/plugins/cases/server/common/utils.test.ts index 322e45094eda4..dba9ff8d829a2 100644 --- a/x-pack/plugins/cases/server/common/utils.test.ts +++ b/x-pack/plugins/cases/server/common/utils.test.ts @@ -5,13 +5,14 @@ * 2.0. */ -import { SavedObjectsFindResponse } from 'kibana/server'; +import { SavedObject, SavedObjectsFindResponse } from 'kibana/server'; import { SECURITY_SOLUTION_OWNER } from '../../common'; import { AssociationType, CaseResponse, CommentAttributes, CommentRequest, + CommentRequestUserType, CommentType, } from '../../common/api'; import { @@ -29,6 +30,8 @@ import { transformComments, flattenCommentSavedObjects, flattenCommentSavedObject, + extractLensReferencesFromCommentString, + getOrUpdateLensReferences, } from './utils'; interface CommentReference { @@ -868,4 +871,113 @@ describe('common utils', () => { ).toEqual(2); }); }); + + describe('extractLensReferencesFromCommentString', () => { + const commentString = [ + '**Test** ', + 'Amazingg!!!', + '!{lens{"timeRange":{"from":"now-7d","to":"now","mode":"relative"},"attributes":{"title":"aaaa","type":"lens","visualizationType":"lnsXY","state":{"datasourceStates":{"indexpattern":{"layers":{"layer1":{"columnOrder":["col1","col2"],"columns":{"col2":{"dataType":"number","isBucketed":false,"label":"Count of records","operationType":"count","scale":"ratio","sourceField":"Records"},"col1":{"dataType":"date","isBucketed":true,"label":"@timestamp","operationType":"date_histogram","params":{"interval":"auto"},"scale":"interval","sourceField":"timestamp"}}}}}},"visualization":{"axisTitlesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"fittingFunction":"None","gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"layers":[{"accessors":["col2"],"layerId":"layer1","seriesType":"bar_stacked","xAccessor":"col1","yConfig":[{"forAccessor":"col2"}]}],"legend":{"isVisible":true,"position":"right"},"preferredSeriesType":"bar_stacked","tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"valueLabels":"hide","yRightExtent":{"mode":"full"}},"query":{"language":"kuery","query":""},"filters":[]},"references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b246","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b248","name":"indexpattern-datasource-layer-layer1"}]},"editMode":false}}', + '!{lens{"timeRange":{"from":"now-7d","to":"now","mode":"relative"},"attributes":{"title":"aaaa","type":"lens","visualizationType":"lnsXY","state":{"datasourceStates":{"indexpattern":{"layers":{"layer1":{"columnOrder":["col1","col2"],"columns":{"col2":{"dataType":"number","isBucketed":false,"label":"Count of records","operationType":"count","scale":"ratio","sourceField":"Records"},"col1":{"dataType":"date","isBucketed":true,"label":"@timestamp","operationType":"date_histogram","params":{"interval":"auto"},"scale":"interval","sourceField":"timestamp"}}}}}},"visualization":{"axisTitlesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"fittingFunction":"None","gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"layers":[{"accessors":["col2"],"layerId":"layer1","seriesType":"bar_stacked","xAccessor":"col1","yConfig":[{"forAccessor":"col2"}]}],"legend":{"isVisible":true,"position":"right"},"preferredSeriesType":"bar_stacked","tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"valueLabels":"hide","yRightExtent":{"mode":"full"}},"query":{"language":"kuery","query":""},"filters":[]},"references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b246","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-layer1"}]},"editMode":false}}', + ].join('\n\n'); + + const extractedReferences = extractLensReferencesFromCommentString(commentString); + + const expectedReferences = [ + { + type: 'index-pattern', + id: '90943e30-9a47-11e8-b64d-95841ca0b246', + name: 'indexpattern-datasource-current-indexpattern', + }, + { + type: 'index-pattern', + id: '90943e30-9a47-11e8-b64d-95841ca0b248', + name: 'indexpattern-datasource-layer-layer1', + }, + { + type: 'index-pattern', + id: '90943e30-9a47-11e8-b64d-95841ca0b247', + name: 'indexpattern-datasource-layer-layer1', + }, + ]; + + expect(expectedReferences.length).toEqual(extractedReferences.length); + expect(expectedReferences).toEqual(expect.arrayContaining(extractedReferences)); + }); + + describe('getOrUpdateLensReferences', () => { + const currentCommentStringReferences = [ + [ + { + type: 'index-pattern', + id: '90943e30-9a47-11e8-b64d-95841ca0b246', + name: 'indexpattern-datasource-current-indexpattern', + }, + { + type: 'index-pattern', + id: '90943e30-9a47-11e8-b64d-95841ca0b248', + name: 'indexpattern-datasource-layer-layer1', + }, + ], + [ + { + type: 'index-pattern', + id: '90943e30-9a47-11e8-b64d-95841ca0b246', + name: 'indexpattern-datasource-current-indexpattern', + }, + { + type: 'index-pattern', + id: '90943e30-9a47-11e8-b64d-95841ca0b248', + name: 'indexpattern-datasource-layer-layer1', + }, + ], + ]; + const currentCommentString = [ + '**Test** ', + `!{lens{"timeRange":{"from":"now-7d","to":"now","mode":"relative"},"attributes":{"title":"aaaa","type":"lens","visualizationType":"lnsXY","state":{"datasourceStates":{"indexpattern":{"layers":{"layer1":{"columnOrder":["col1","col2"],"columns":{"col2":{"dataType":"number","isBucketed":false,"label":"Count of records","operationType":"count","scale":"ratio","sourceField":"Records"},"col1":{"dataType":"date","isBucketed":true,"label":"@timestamp","operationType":"date_histogram","params":{"interval":"auto"},"scale":"interval","sourceField":"timestamp"}}}}}},"visualization":{"axisTitlesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"fittingFunction":"None","gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"layers":[{"accessors":["col2"],"layerId":"layer1","seriesType":"bar_stacked","xAccessor":"col1","yConfig":[{"forAccessor":"col2"}]}],"legend":{"isVisible":true,"position":"right"},"preferredSeriesType":"bar_stacked","tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"valueLabels":"hide","yRightExtent":{"mode":"full"}},"query":{"language":"kuery","query":""},"filters":[]},"references":${JSON.stringify( + currentCommentStringReferences[0] + )}},"editMode":false}}`, + `!{lens{"timeRange":{"from":"now-7d","to":"now","mode":"relative"},"attributes":{"title":"aaaa","type":"lens","visualizationType":"lnsXY","state":{"datasourceStates":{"indexpattern":{"layers":{"layer1":{"columnOrder":["col1","col2"],"columns":{"col2":{"dataType":"number","isBucketed":false,"label":"Count of records","operationType":"count","scale":"ratio","sourceField":"Records"},"col1":{"dataType":"date","isBucketed":true,"label":"@timestamp","operationType":"date_histogram","params":{"interval":"auto"},"scale":"interval","sourceField":"timestamp"}}}}}},"visualization":{"axisTitlesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"fittingFunction":"None","gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"layers":[{"accessors":["col2"],"layerId":"layer1","seriesType":"bar_stacked","xAccessor":"col1","yConfig":[{"forAccessor":"col2"}]}],"legend":{"isVisible":true,"position":"right"},"preferredSeriesType":"bar_stacked","tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"valueLabels":"hide","yRightExtent":{"mode":"full"}},"query":{"language":"kuery","query":""},"filters":[]},"references":${JSON.stringify( + currentCommentStringReferences[1] + )}},"editMode":false}}`, + ].join('\n\n'); + const nonLensCurrentCommentReferences = [ + { type: 'case', id: '7b4be181-9646-41b8-b12d-faabf1bd9512', name: 'Test case' }, + { type: 'timeline', id: '0f847d31-9683-4ebd-92b9-454e3e39aec1', name: 'Test case timeline' }, + ]; + const currentCommentReferences = [ + ...currentCommentStringReferences.flat(), + ...nonLensCurrentCommentReferences, + ]; + const newCommentStringReferences = [ + { + type: 'index-pattern', + id: '90943e30-9a47-11e8-b64d-95841ca0b245', + name: 'indexpattern-datasource-current-indexpattern', + }, + { + type: 'index-pattern', + id: '90943e30-9a47-11e8-b64d-95841ca0b248', + name: 'indexpattern-datasource-layer-layer1', + }, + ]; + const newCommentString = [ + '**Test** ', + 'Awmazingg!!!', + `!{lens{"timeRange":{"from":"now-7d","to":"now","mode":"relative"},"attributes":{"title":"aaaa","type":"lens","visualizationType":"lnsXY","state":{"datasourceStates":{"indexpattern":{"layers":{"layer1":{"columnOrder":["col1","col2"],"columns":{"col2":{"dataType":"number","isBucketed":false,"label":"Count of records","operationType":"count","scale":"ratio","sourceField":"Records"},"col1":{"dataType":"date","isBucketed":true,"label":"@timestamp","operationType":"date_histogram","params":{"interval":"auto"},"scale":"interval","sourceField":"timestamp"}}}}}},"visualization":{"axisTitlesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"fittingFunction":"None","gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"layers":[{"accessors":["col2"],"layerId":"layer1","seriesType":"bar_stacked","xAccessor":"col1","yConfig":[{"forAccessor":"col2"}]}],"legend":{"isVisible":true,"position":"right"},"preferredSeriesType":"bar_stacked","tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"valueLabels":"hide","yRightExtent":{"mode":"full"}},"query":{"language":"kuery","query":""},"filters":[]},"references":${JSON.stringify( + newCommentStringReferences + )}},"editMode":false}}`, + ].join('\n\n'); + + const updatedReferences = getOrUpdateLensReferences(newCommentString, { + references: currentCommentReferences, + attributes: { + comment: currentCommentString, + }, + } as SavedObject); + + const expectedReferences = [...nonLensCurrentCommentReferences, ...newCommentStringReferences]; + + expect(expectedReferences.length).toEqual(updatedReferences.length); + expect(expectedReferences).toEqual(expect.arrayContaining(updatedReferences)); + }); }); diff --git a/x-pack/plugins/cases/server/common/utils.ts b/x-pack/plugins/cases/server/common/utils.ts index 13d3f3768f391..7350fcd053425 100644 --- a/x-pack/plugins/cases/server/common/utils.ts +++ b/x-pack/plugins/cases/server/common/utils.ts @@ -4,11 +4,23 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import Boom from '@hapi/boom'; -import { SavedObjectsFindResult, SavedObjectsFindResponse, SavedObject } from 'kibana/server'; -import { isEmpty } from 'lodash'; +import Boom from '@hapi/boom'; +import unified from 'unified'; +import type { Node } from 'unist'; +// installed by @elastic/eui +// eslint-disable-next-line import/no-extraneous-dependencies +import markdown from 'remark-parse'; +import { + SavedObjectsFindResult, + SavedObjectsFindResponse, + SavedObject, + SavedObjectReference, +} from 'kibana/server'; +import { filter, flatMap, uniqWith, isEmpty, xorWith } from 'lodash'; +import { TimeRange } from 'src/plugins/data/server'; import { AlertInfo } from '.'; +import { LensDocShape714 } from '../../../lens/server'; import { AssociationType, @@ -35,6 +47,7 @@ import { User, } from '../../common'; import { UpdateAlertRequest } from '../client/alerts/types'; +import { LensParser, ID as LENS_ID } from './lens_parser'; /** * Default sort field for querying saved objects. @@ -430,3 +443,51 @@ export function checkEnabledCaseConnectorOrThrow(subCaseID: string | undefined) ); } } + +interface LensMarkdownNode { + timeRange: TimeRange; + editMode: boolean; + attributes: LensDocShape714 & { references: SavedObjectReference[] }; +} + +interface LensMarkdownParent extends Node { + children: LensMarkdownNode[]; +} + +export const extractLensReferencesFromCommentString = (comment: string): SavedObjectReference[] => { + const processor = unified().use([[markdown, {}], LensParser]); + const parsedComment = processor.parse(comment) as LensMarkdownParent; + const lensReferences = filter(parsedComment.children, { type: LENS_ID }) as LensMarkdownNode[]; + const flattenRefs = flatMap(lensReferences, (lensObject) => lensObject.attributes.references); + + const uniqRefs = uniqWith( + flattenRefs, + (refA, refB) => refA.type === refB.type && refA.id === refB.id + ); + + return uniqRefs; +}; + +export const getOrUpdateLensReferences = ( + newComment: string, + currentComment?: SavedObject +) => { + if (!currentComment) { + return extractLensReferencesFromCommentString(newComment); + } + + const savedObjectReferences = currentComment.references; + const savedObjectLensReferences = extractLensReferencesFromCommentString( + currentComment.attributes.comment + ); + + const currentNonLensReferences = xorWith( + savedObjectReferences, + savedObjectLensReferences, + (refA, refB) => refA.type === refB.type && refA.id === refB.id + ); + + const newCommentLensReferences = extractLensReferencesFromCommentString(newComment); + + return currentNonLensReferences.concat(newCommentLensReferences); +}; diff --git a/x-pack/plugins/cases/server/services/attachments/index.ts b/x-pack/plugins/cases/server/services/attachments/index.ts index c2d9b4826fc14..66f5e6cf43be7 100644 --- a/x-pack/plugins/cases/server/services/attachments/index.ts +++ b/x-pack/plugins/cases/server/services/attachments/index.ts @@ -5,7 +5,12 @@ * 2.0. */ -import { Logger, SavedObject, SavedObjectReference } from 'kibana/server'; +import { + Logger, + SavedObject, + SavedObjectReference, + SavedObjectsUpdateOptions, +} from 'kibana/server'; import { KueryNode } from '../../../../../../src/plugins/data/common'; import { @@ -38,10 +43,10 @@ interface CreateAttachmentArgs extends ClientArgs { interface UpdateArgs { attachmentId: string; updatedAttributes: AttachmentPatchAttributes; - version?: string; + options: SavedObjectsUpdateOptions; } -type UpdateAttachmentArgs = UpdateArgs & ClientArgs; +export type UpdateAttachmentArgs = UpdateArgs & ClientArgs; interface BulkUpdateAttachmentArgs extends ClientArgs { comments: UpdateArgs[]; @@ -142,7 +147,7 @@ export class AttachmentService { unsecuredSavedObjectsClient, attachmentId, updatedAttributes, - version, + options, }: UpdateAttachmentArgs) { try { this.log.debug(`Attempting to UPDATE comment ${attachmentId}`); @@ -150,7 +155,7 @@ export class AttachmentService { CASE_COMMENT_SAVED_OBJECT, attachmentId, updatedAttributes, - { version } + options ); } catch (error) { this.log.error(`Error on UPDATE comment ${attachmentId}: ${error}`); @@ -168,7 +173,7 @@ export class AttachmentService { type: CASE_COMMENT_SAVED_OBJECT, id: c.attachmentId, attributes: c.updatedAttributes, - version: c.version, + ...c.options, })) ); } catch (error) { diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index 56d747e609149..7ba22836c5880 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -314,9 +314,12 @@ export class LensPlugin { return { EmbeddableComponent: getEmbeddableComponent(core, startDependencies), SaveModalComponent: getSaveModalComponent(core, startDependencies, this.attributeService!), - navigateToPrefilledEditor: (input, options) => { + navigateToPrefilledEditor: ( + input, + { openInNewTab = false, originatingApp = '', originatingPath } = {} + ) => { // for openInNewTab, we set the time range in url via getEditPath below - if (input.timeRange && !options?.openInNewTab) { + if (input.timeRange && !openInNewTab) { startDependencies.data.query.timefilter.timefilter.setTime(input.timeRange); } const transfer = new EmbeddableStateTransfer( @@ -324,11 +327,11 @@ export class LensPlugin { core.application.currentAppId$ ); transfer.navigateToEditor(APP_ID, { - openInNewTab: options?.openInNewTab ?? false, - path: getEditPath(undefined, options?.openInNewTab ? input.timeRange : undefined), + openInNewTab, + path: getEditPath(undefined, openInNewTab ? input.timeRange : undefined), state: { - originatingApp: options?.originatingApp ?? '', - originatingPath: options?.originatingPath, + originatingApp, + originatingPath, valueInput: input, }, }); diff --git a/x-pack/plugins/lens/server/index.ts b/x-pack/plugins/lens/server/index.ts index b61282c9e26e5..76c2d81a75d49 100644 --- a/x-pack/plugins/lens/server/index.ts +++ b/x-pack/plugins/lens/server/index.ts @@ -9,6 +9,7 @@ import { PluginInitializerContext, PluginConfigDescriptor } from 'kibana/server' import { LensServerPlugin } from './plugin'; export * from './plugin'; +export * from './migrations/types'; import { configSchema, ConfigSchema } from '../config'; From 42b0c7e919a3fdb02d9ef307778cc39a0bbd15bd Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Mon, 19 Jul 2021 15:35:35 +0200 Subject: [PATCH 23/41] migrations --- x-pack/plugins/cases/server/common/utils.ts | 42 ++++- x-pack/plugins/cases/server/plugin.ts | 8 +- .../server/saved_object_types/comments.ts | 12 +- .../cases/server/saved_object_types/index.ts | 2 +- .../saved_object_types/migrations.test.ts | 166 ++++++++++++++++++ .../server/saved_object_types/migrations.ts | 128 ++++++++++---- .../server/services/attachments/index.ts | 2 +- x-pack/plugins/lens/server/plugin.tsx | 11 +- 8 files changed, 315 insertions(+), 56 deletions(-) create mode 100644 x-pack/plugins/cases/server/saved_object_types/migrations.test.ts diff --git a/x-pack/plugins/cases/server/common/utils.ts b/x-pack/plugins/cases/server/common/utils.ts index 7350fcd053425..5dbfee1a3b8c3 100644 --- a/x-pack/plugins/cases/server/common/utils.ts +++ b/x-pack/plugins/cases/server/common/utils.ts @@ -7,10 +7,13 @@ import Boom from '@hapi/boom'; import unified from 'unified'; -import type { Node } from 'unist'; +import type { Node, Parent } from 'unist'; // installed by @elastic/eui // eslint-disable-next-line import/no-extraneous-dependencies import markdown from 'remark-parse'; +// eslint-disable-next-line import/no-extraneous-dependencies +import remarkStringify from 'remark-stringify'; + import { SavedObjectsFindResult, SavedObjectsFindResponse, @@ -450,15 +453,38 @@ interface LensMarkdownNode { attributes: LensDocShape714 & { references: SavedObjectReference[] }; } -interface LensMarkdownParent extends Node { - children: LensMarkdownNode[]; -} +export const parseCommentString = (comment: string) => { + const processor = unified().use([[markdown, {}], LensParser]); + return processor.parse(comment) as Parent; +}; + +export const stringifyComment = (comment: Parent) => + unified() + .use([ + [ + remarkStringify, + { + allowDangerousHtml: true, + handlers: { + lens: (a) => + `!{lens${JSON.stringify({ + timeRange: a.timeRange, + editMode: a.editMode, + attributes: a.attributes, + })}}`, + }, + }, + ], + ]) + .stringify(comment); + +export const getLensVisualizations = (parsedComment: Array) => + filter(parsedComment, { type: LENS_ID }) as LensMarkdownNode[]; export const extractLensReferencesFromCommentString = (comment: string): SavedObjectReference[] => { - const processor = unified().use([[markdown, {}], LensParser]); - const parsedComment = processor.parse(comment) as LensMarkdownParent; - const lensReferences = filter(parsedComment.children, { type: LENS_ID }) as LensMarkdownNode[]; - const flattenRefs = flatMap(lensReferences, (lensObject) => lensObject.attributes.references); + const parsedComment = parseCommentString(comment); + const lensVisualizations = getLensVisualizations(parsedComment.children); + const flattenRefs = flatMap(lensVisualizations, (lensObject) => lensObject.attributes.references); const uniqRefs = uniqWith( flattenRefs, diff --git a/x-pack/plugins/cases/server/plugin.ts b/x-pack/plugins/cases/server/plugin.ts index b1e2f61a595ee..59a06d8e99208 100644 --- a/x-pack/plugins/cases/server/plugin.ts +++ b/x-pack/plugins/cases/server/plugin.ts @@ -8,6 +8,7 @@ import { IContextProvider, KibanaRequest, Logger, PluginInitializerContext } from 'kibana/server'; import { CoreSetup, CoreStart } from 'src/core/server'; +import { LensPluginSetup } from '../../lens/server'; import { SecurityPluginSetup, SecurityPluginStart } from '../../security/server'; import { PluginSetupContract as ActionsPluginSetup, @@ -18,7 +19,7 @@ import { APP_ID, ENABLE_CASE_CONNECTOR } from '../common'; import { ConfigType } from './config'; import { initCaseApi } from './routes/api'; import { - caseCommentSavedObjectType, + createCaseCommentSavedObjectType, caseConfigureSavedObjectType, caseConnectorMappingsSavedObjectType, caseSavedObjectType, @@ -40,6 +41,7 @@ function createConfig(context: PluginInitializerContext) { export interface PluginsSetup { security?: SecurityPluginSetup; actions: ActionsPluginSetup; + lens: LensPluginSetup; } export interface PluginsStart { @@ -81,7 +83,9 @@ export class CasePlugin { this.securityPluginSetup = plugins.security; - core.savedObjects.registerType(caseCommentSavedObjectType); + core.savedObjects.registerType( + createCaseCommentSavedObjectType({ getLensMigrations: plugins.lens.getAllMigrations }) + ); core.savedObjects.registerType(caseConfigureSavedObjectType); core.savedObjects.registerType(caseConnectorMappingsSavedObjectType); core.savedObjects.registerType(caseSavedObjectType); diff --git a/x-pack/plugins/cases/server/saved_object_types/comments.ts b/x-pack/plugins/cases/server/saved_object_types/comments.ts index 876ceb9bc2045..4d541778648dc 100644 --- a/x-pack/plugins/cases/server/saved_object_types/comments.ts +++ b/x-pack/plugins/cases/server/saved_object_types/comments.ts @@ -7,11 +7,13 @@ import { SavedObjectsType } from 'src/core/server'; import { CASE_COMMENT_SAVED_OBJECT } from '../../common'; -import { commentsMigrations } from './migrations'; +import { createCommentsMigrations, CreateCommentsMigrationsDeps } from './migrations'; -export const caseCommentSavedObjectType: SavedObjectsType = { +export const createCaseCommentSavedObjectType = ( + migrationDeps: CreateCommentsMigrationsDeps +): SavedObjectsType => ({ name: CASE_COMMENT_SAVED_OBJECT, - hidden: true, + hidden: false, namespaceType: 'single', mappings: { properties: { @@ -105,5 +107,5 @@ export const caseCommentSavedObjectType: SavedObjectsType = { }, }, }, - migrations: commentsMigrations, -}; + migrations: () => createCommentsMigrations(migrationDeps), +}); diff --git a/x-pack/plugins/cases/server/saved_object_types/index.ts b/x-pack/plugins/cases/server/saved_object_types/index.ts index 1c6bcf6ca710a..2c39a10f61da7 100644 --- a/x-pack/plugins/cases/server/saved_object_types/index.ts +++ b/x-pack/plugins/cases/server/saved_object_types/index.ts @@ -8,6 +8,6 @@ export { caseSavedObjectType } from './cases'; export { subCaseSavedObjectType } from './sub_case'; export { caseConfigureSavedObjectType } from './configure'; -export { caseCommentSavedObjectType } from './comments'; +export { createCaseCommentSavedObjectType } from './comments'; export { caseUserActionSavedObjectType } from './user_actions'; export { caseConnectorMappingsSavedObjectType } from './connector_mappings'; diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations.test.ts b/x-pack/plugins/cases/server/saved_object_types/migrations.test.ts new file mode 100644 index 0000000000000..8db18f983cdfa --- /dev/null +++ b/x-pack/plugins/cases/server/saved_object_types/migrations.test.ts @@ -0,0 +1,166 @@ +/* + * 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. + */ + +import { + LensDocShape, + migrations as lensMigrations, +} from '../../../lens/server/migrations/saved_object_migrations'; +import { createCommentsMigrations } from './migrations'; +import { SavedObjectMigrationFn } from 'src/core/server'; +import { getLensVisualizations, parseCommentString } from '../common'; + +describe('Comments migrations', () => { + describe('7.14.0 remove time zone from Lens visualization date histogram', () => { + const lensVisualizationToMigrate = { + title: 'MyRenamedOps', + description: '', + visualizationType: 'lnsXY', + state: { + datasourceStates: { + indexpattern: { + layers: { + '2': { + columns: { + '3': { + label: '@timestamp', + dataType: 'date', + operationType: 'date_histogram', + sourceField: '@timestamp', + isBucketed: true, + scale: 'interval', + params: { interval: 'auto', timeZone: 'Europe/Berlin' }, + }, + '4': { + label: '@timestamp', + dataType: 'date', + operationType: 'date_histogram', + sourceField: '@timestamp', + isBucketed: true, + scale: 'interval', + params: { interval: 'auto' }, + }, + '5': { + label: '@timestamp', + dataType: 'date', + operationType: 'my_unexpected_operation', + isBucketed: true, + scale: 'interval', + params: { timeZone: 'do not delete' }, + }, + }, + columnOrder: ['3', '4', '5'], + incompleteColumns: {}, + }, + }, + }, + }, + visualization: { + title: 'Empty XY chart', + legend: { isVisible: true, position: 'right' }, + valueLabels: 'hide', + preferredSeriesType: 'bar_stacked', + layers: [ + { + layerId: '5ab74ddc-93ca-44e2-9857-ecf85c86b53e', + accessors: [ + '5fea2a56-7b73-44b5-9a50-7f0c0c4f8fd0', + 'e5efca70-edb5-4d6d-a30a-79384066987e', + '7ffb7bde-4f42-47ab-b74d-1b4fd8393e0f', + ], + position: 'top', + seriesType: 'bar_stacked', + showGridlines: false, + xAccessor: '2e57a41e-5a52-42d3-877f-bd211d903ef8', + }, + ], + }, + query: { query: '', language: 'kuery' }, + filters: [], + }, + }; + + const caseComment = { + type: 'cases-comments', + id: '1cefd0d0-e86d-11eb-bae5-3d065cd16a32', + attributes: { + associationType: 'case', + comment: `Amaing\n\n**!!!**\n\n!{lens{"timeRange":{"from":"now-7d","to":"now","mode":"relative"},"editMode":false,"attributes":${JSON.stringify( + lensVisualizationToMigrate + )}}}`, + type: 'user', + created_at: '2021-07-19T08:41:29.951Z', + created_by: { + email: null, + full_name: null, + username: 'elastic', + }, + pushed_at: null, + pushed_by: null, + updated_at: '2021-07-19T08:41:47.549Z', + updated_by: { + full_name: null, + email: null, + username: 'elastic', + }, + }, + references: [ + { + name: 'associated-cases', + id: '77d1b230-d35e-11eb-8da6-6f746b9cb499', + type: 'cases', + }, + { + name: 'indexpattern-datasource-current-indexpattern', + id: '90943e30-9a47-11e8-b64d-95841ca0b247', + type: 'index-pattern', + }, + { + name: 'indexpattern-datasource-current-indexpattern', + id: '90943e30-9a47-11e8-b64d-95841ca0b247', + type: 'index-pattern', + }, + ], + migrationVersion: { + 'cases-comments': '7.14.0', + }, + coreMigrationVersion: '8.0.0', + updated_at: '2021-07-19T08:41:47.552Z', + version: 'WzgxMTY4MSw5XQ==', + namespaces: ['default'], + score: 0, + }; + + it('should remove time zone param from date histogram', () => { + const commentsMigrations714 = createCommentsMigrations({ + getLensMigrations: () => lensMigrations, + }); + const result = commentsMigrations714['7.14.0'](caseComment) as ReturnType< + SavedObjectMigrationFn< + LensDocShape, + { + comment: string; + } + > + >; + const parsedComment = parseCommentString(result.attributes.comment); + const lensVisualizations = getLensVisualizations(parsedComment.children); + + const layers = Object.values( + lensVisualizations[0].attributes.state.datasourceStates.indexpattern.layers + ); + expect(layers.length).toBe(1); + const columns = Object.values(layers[0].columns); + expect(columns.length).toBe(3); + expect(columns[0].operationType).toEqual('date_histogram'); + expect((columns[0] as { params: {} }).params).toEqual({ interval: 'auto' }); + expect(columns[1].operationType).toEqual('date_histogram'); + expect((columns[1] as { params: {} }).params).toEqual({ interval: 'auto' }); + expect(columns[2].operationType).toEqual('my_unexpected_operation'); + expect((columns[2] as { params: {} }).params).toEqual({ timeZone: 'do not delete' }); + }); + }); +}); diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations.ts b/x-pack/plugins/cases/server/saved_object_types/migrations.ts index e4b201b21b756..af2197b6a413a 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations.ts @@ -7,7 +7,18 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import { SavedObjectUnsanitizedDoc, SavedObjectSanitizedDoc } from '../../../../../src/core/server'; +import { flow, mapValues } from 'lodash'; +import { LensPluginSetup } from '../../../lens/server'; +import { + mergeMigrationFunctionMaps, + MigrateFunction, + MigrateFunctionsObject, +} from '../../../../../src/plugins/kibana_utils/common'; +import { + SavedObjectUnsanitizedDoc, + SavedObjectSanitizedDoc, + SavedObjectMigrationFn, +} from '../../../../../src/core/server'; import { ConnectorTypes, CommentType, @@ -16,6 +27,7 @@ import { ESConnectorFields, SECURITY_SOLUTION_OWNER, } from '../../common'; +import { parseCommentString, stringifyComment } from '../common'; interface UnsanitizedCaseConnector { connector_id: string; @@ -224,44 +236,86 @@ interface SanitizedCommentForSubCases { rule?: { id: string | null; name: string | null }; } -export const commentsMigrations = { - '7.11.0': ( - doc: SavedObjectUnsanitizedDoc - ): SavedObjectSanitizedDoc => { - return { - ...doc, - attributes: { - ...doc.attributes, - type: CommentType.user, - }, - references: doc.references || [], - }; - }, - '7.12.0': ( - doc: SavedObjectUnsanitizedDoc - ): SavedObjectSanitizedDoc => { - let attributes: SanitizedCommentForSubCases & UnsanitizedComment = { - ...doc.attributes, - associationType: AssociationType.case, - }; - - // only add the rule object for alert comments. Prior to 7.12 we only had CommentType.alert, generated alerts are - // introduced in 7.12. - if (doc.attributes.type === CommentType.alert) { - attributes = { ...attributes, rule: { id: null, name: null } }; +const migrateByValueLensVisualizations = ( + migrate: MigrateFunction, + version: string +): SavedObjectMigrationFn => (doc: any) => { + const parsedComment = parseCommentString(doc.attributes.comment); + const migratedComment = parsedComment.children.map((comment) => { + if (comment?.type === 'lens') { + // @ts-expect-error + return migrate(comment); } - return { - ...doc, - attributes, - references: doc.references || [], - }; - }, - '7.14.0': ( - doc: SavedObjectUnsanitizedDoc> - ): SavedObjectSanitizedDoc => { - return addOwnerToSO(doc); - }, + return comment; + }); + + // @ts-expect-error + parsedComment.children = migratedComment; + doc.attributes.comment = stringifyComment(parsedComment); + + return doc; +}; + +export interface CreateCommentsMigrationsDeps { + getLensMigrations: LensPluginSetup['getAllMigrations']; +} + +export const createCommentsMigrations = (migrationDeps: CreateCommentsMigrationsDeps) => { + // @ts-expect-error + const lensMigrations = mapValues( + // @ts-expect-error + migrationDeps.getLensMigrations(), + migrateByValueLensVisualizations + ) as MigrateFunctionsObject; + + const commentsMigrations = { + '7.11.0': flow( + ( + doc: SavedObjectUnsanitizedDoc + ): SavedObjectSanitizedDoc => { + return { + ...doc, + attributes: { + ...doc.attributes, + type: CommentType.user, + }, + references: doc.references || [], + }; + } + ), + '7.12.0': flow( + ( + doc: SavedObjectUnsanitizedDoc + ): SavedObjectSanitizedDoc => { + let attributes: SanitizedCommentForSubCases & UnsanitizedComment = { + ...doc.attributes, + associationType: AssociationType.case, + }; + + // only add the rule object for alert comments. Prior to 7.12 we only had CommentType.alert, generated alerts are + // introduced in 7.12. + if (doc.attributes.type === CommentType.alert) { + attributes = { ...attributes, rule: { id: null, name: null } }; + } + + return { + ...doc, + attributes, + references: doc.references || [], + }; + } + ), + '7.14.0': flow( + ( + doc: SavedObjectUnsanitizedDoc> + ): SavedObjectSanitizedDoc => { + return addOwnerToSO(doc); + } + ), + }; + + return mergeMigrationFunctionMaps(commentsMigrations, lensMigrations); }; export const connectorMappingsMigrations = { diff --git a/x-pack/plugins/cases/server/services/attachments/index.ts b/x-pack/plugins/cases/server/services/attachments/index.ts index 66f5e6cf43be7..105b6a3125523 100644 --- a/x-pack/plugins/cases/server/services/attachments/index.ts +++ b/x-pack/plugins/cases/server/services/attachments/index.ts @@ -43,7 +43,7 @@ interface CreateAttachmentArgs extends ClientArgs { interface UpdateArgs { attachmentId: string; updatedAttributes: AttachmentPatchAttributes; - options: SavedObjectsUpdateOptions; + options?: SavedObjectsUpdateOptions; } export type UpdateAttachmentArgs = UpdateArgs & ClientArgs; diff --git a/x-pack/plugins/lens/server/plugin.tsx b/x-pack/plugins/lens/server/plugin.tsx index c23c98cd12aec..6577bbafc2a8b 100644 --- a/x-pack/plugins/lens/server/plugin.tsx +++ b/x-pack/plugins/lens/server/plugin.tsx @@ -19,6 +19,7 @@ import { import { setupSavedObjects } from './saved_objects'; import { EmbeddableSetup } from '../../../../src/plugins/embeddable/server'; import { lensEmbeddableFactory } from './embeddable/lens_embeddable_factory'; +import { migrations } from './migrations/saved_object_migrations'; export interface PluginSetupContract { usageCollection?: UsageCollectionSetup; @@ -31,6 +32,10 @@ export interface PluginStartContract { data: DataPluginStart; } +export interface LensPluginSetup { + getAllMigrations: () => typeof migrations; +} + export class LensServerPlugin implements Plugin<{}, {}, {}, {}> { private readonly kibanaIndexConfig: Observable<{ kibana: { index: string } }>; private readonly telemetryLogger: Logger; @@ -39,7 +44,7 @@ export class LensServerPlugin implements Plugin<{}, {}, {}, {}> { this.kibanaIndexConfig = initializerContext.config.legacy.globalConfig$; this.telemetryLogger = initializerContext.logger.get('usage'); } - setup(core: CoreSetup, plugins: PluginSetupContract) { + setup(core: CoreSetup, plugins: PluginSetupContract): LensPluginSetup { setupSavedObjects(core); setupRoutes(core, this.initializerContext.logger.get()); if (plugins.usageCollection && plugins.taskManager) { @@ -57,7 +62,9 @@ export class LensServerPlugin implements Plugin<{}, {}, {}, {}> { ); } plugins.embeddable.registerEmbeddableFactory(lensEmbeddableFactory()); - return {}; + return { + getAllMigrations: () => migrations, + }; } start(core: CoreStart, plugins: PluginStartContract) { From bfacd399491b023dd17c13344af4d2d6aee78ad9 Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Mon, 19 Jul 2021 16:10:22 +0200 Subject: [PATCH 24/41] hidden --- package.json | 1 + x-pack/plugins/cases/server/common/utils.ts | 1 + x-pack/plugins/cases/server/saved_object_types/comments.ts | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 0875242a18da9..fc29f0a12f4d9 100644 --- a/package.json +++ b/package.json @@ -805,6 +805,7 @@ "react-test-renderer": "^16.12.0", "read-pkg": "^5.2.0", "regenerate": "^1.4.0", + "remark-stringify": "^9.0.0", "resolve": "^1.7.1", "rxjs-marbles": "^5.0.6", "sass-loader": "^8.0.2", diff --git a/x-pack/plugins/cases/server/common/utils.ts b/x-pack/plugins/cases/server/common/utils.ts index 5dbfee1a3b8c3..2690c454c3311 100644 --- a/x-pack/plugins/cases/server/common/utils.ts +++ b/x-pack/plugins/cases/server/common/utils.ts @@ -466,6 +466,7 @@ export const stringifyComment = (comment: Parent) => { allowDangerousHtml: true, handlers: { + // @ts-expect-error lens: (a) => `!{lens${JSON.stringify({ timeRange: a.timeRange, diff --git a/x-pack/plugins/cases/server/saved_object_types/comments.ts b/x-pack/plugins/cases/server/saved_object_types/comments.ts index 4d541778648dc..67d0b1ccfe635 100644 --- a/x-pack/plugins/cases/server/saved_object_types/comments.ts +++ b/x-pack/plugins/cases/server/saved_object_types/comments.ts @@ -13,7 +13,7 @@ export const createCaseCommentSavedObjectType = ( migrationDeps: CreateCommentsMigrationsDeps ): SavedObjectsType => ({ name: CASE_COMMENT_SAVED_OBJECT, - hidden: false, + hidden: true, namespaceType: 'single', mappings: { properties: { From f5dc789e2eca3c9399a0cbc05d86c0dc5066f6b1 Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Mon, 19 Jul 2021 16:52:53 +0200 Subject: [PATCH 25/41] package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fc29f0a12f4d9..0cd3c76cbb3a1 100644 --- a/package.json +++ b/package.json @@ -380,6 +380,7 @@ "redux-saga": "^1.1.3", "redux-thunk": "^2.3.0", "redux-thunks": "^1.0.0", + "remark-stringify": "^9.0.0", "regenerator-runtime": "^0.13.3", "request": "^2.88.0", "require-in-the-middle": "^5.0.2", @@ -805,7 +806,6 @@ "react-test-renderer": "^16.12.0", "read-pkg": "^5.2.0", "regenerate": "^1.4.0", - "remark-stringify": "^9.0.0", "resolve": "^1.7.1", "rxjs-marbles": "^5.0.6", "sass-loader": "^8.0.2", From e8c5c132122235d5b27341a2436d9e5cbd9c6b49 Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Mon, 19 Jul 2021 19:56:20 +0200 Subject: [PATCH 26/41] cleanup --- x-pack/examples/embedded_lens_example/public/app.tsx | 4 +++- x-pack/plugins/cases/server/common/utils.ts | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/x-pack/examples/embedded_lens_example/public/app.tsx b/x-pack/examples/embedded_lens_example/public/app.tsx index c8f69a606de29..fccd7afa37f09 100644 --- a/x-pack/examples/embedded_lens_example/public/app.tsx +++ b/x-pack/examples/embedded_lens_example/public/app.tsx @@ -196,7 +196,9 @@ export const App = (props: { timeRange: time, attributes: getLensAttributes(props.defaultIndexPattern!, color), }, - false + { + openInNewTab: false, + } ); }} > diff --git a/x-pack/plugins/cases/server/common/utils.ts b/x-pack/plugins/cases/server/common/utils.ts index 2690c454c3311..75bf79cb61866 100644 --- a/x-pack/plugins/cases/server/common/utils.ts +++ b/x-pack/plugins/cases/server/common/utils.ts @@ -11,7 +11,6 @@ import type { Node, Parent } from 'unist'; // installed by @elastic/eui // eslint-disable-next-line import/no-extraneous-dependencies import markdown from 'remark-parse'; -// eslint-disable-next-line import/no-extraneous-dependencies import remarkStringify from 'remark-stringify'; import { From 5d59b6bb850d5ecc3408fe5e056b578643dce19d Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Sun, 1 Aug 2021 13:18:41 +0200 Subject: [PATCH 27/41] update unit tests --- .../components/action_menu/action_menu.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/action_menu/action_menu.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/action_menu/action_menu.test.tsx index 329192abc99d2..09d7ab21cffdf 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/action_menu/action_menu.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/action_menu/action_menu.test.tsx @@ -39,7 +39,7 @@ describe('Action Menu', function () { attributes: sampleAttribute, timeRange: { to: 'now', from: 'now-10m' }, }, - true + { openInNewTab: true } ); }); From 835516ff995f599c0bf7b6029f14fae07ae84b18 Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Wed, 4 Aug 2021 20:42:34 +0200 Subject: [PATCH 28/41] WIP --- .../markdown_editor/plugins/lens/plugin.tsx | 26 ++++--- .../saved_object_types/migrations.test.ts | 78 ++++++++++++++++++- 2 files changed, 90 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx index 7985962b92251..67fe0de3270a9 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx @@ -339,18 +339,20 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ /> - - handleEditInLensClick()} - > - - + + + handleEditInLensClick()} + > + + + ) : null} diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations.test.ts b/x-pack/plugins/cases/server/saved_object_types/migrations.test.ts index 8db18f983cdfa..19781392395a7 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations.test.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations.test.ts @@ -83,14 +83,87 @@ describe('Comments migrations', () => { }, }; + const expectedLensVisualizationMigrated = { + title: 'MyRenamedOps', + description: '', + visualizationType: 'lnsXY', + state: { + datasourceStates: { + indexpattern: { + layers: { + '2': { + columns: { + '3': { + label: '@timestamp', + dataType: 'date', + operationType: 'date_histogram', + sourceField: '@timestamp', + isBucketed: true, + scale: 'interval', + params: { interval: 'auto' }, + }, + '4': { + label: '@timestamp', + dataType: 'date', + operationType: 'date_histogram', + sourceField: '@timestamp', + isBucketed: true, + scale: 'interval', + params: { interval: 'auto' }, + }, + '5': { + label: '@timestamp', + dataType: 'date', + operationType: 'my_unexpected_operation', + isBucketed: true, + scale: 'interval', + params: { timeZone: 'do not delete' }, + }, + }, + columnOrder: ['3', '4', '5'], + incompleteColumns: {}, + }, + }, + }, + }, + visualization: { + title: 'Empty XY chart', + legend: { isVisible: true, position: 'right' }, + valueLabels: 'hide', + preferredSeriesType: 'bar_stacked', + layers: [ + { + layerId: '5ab74ddc-93ca-44e2-9857-ecf85c86b53e', + accessors: [ + '5fea2a56-7b73-44b5-9a50-7f0c0c4f8fd0', + 'e5efca70-edb5-4d6d-a30a-79384066987e', + '7ffb7bde-4f42-47ab-b74d-1b4fd8393e0f', + ], + position: 'top', + seriesType: 'bar_stacked', + showGridlines: false, + xAccessor: '2e57a41e-5a52-42d3-877f-bd211d903ef8', + }, + ], + }, + query: { query: '', language: 'kuery' }, + filters: [], + }, + }; + + const expectedMigrationCommentResult = `"**Amazing**\n\n!{tooltip[Tessss](https://regex101.com/r/qQ1rvs/1)}\n\nbrbrbr\n\n[asdasdasdasd](http://localhost:5601/moq/app/security/timelines?timeline=\\(id%3A%27e4362a60-f478-11eb-a4b0-ebefce184d8d%27%2CisOpen%3A!t\\))\n\n!{lens{\"timeRange\":{\"from\":\"now-7d\",\"to\":\"now\",\"mode\":\"relative\"},\"editMode\":false,\"attributes\":${JSON.stringify( + expectedLensVisualizationMigrated + )}}}\n\n!{lens{\"timeRange\":{\"from\":\"now-7d\",\"to\":\"now\",\"mode\":\"relative\"},\"editMode\":false,\"attributes\":{\"title\":\"TEst22\",\"type\":\"lens\",\"visualizationType\":\"lnsMetric\",\"state\":{\"datasourceStates\":{\"indexpattern\":{\"layers\":{\"layer1\":{\"columnOrder\":[\"col2\"],\"columns\":{\"col2\":{\"dataType\":\"number\",\"isBucketed\":false,\"label\":\"Count of records\",\"operationType\":\"count\",\"scale\":\"ratio\",\"sourceField\":\"Records\"}}}}}},\"visualization\":{\"layerId\":\"layer1\",\"accessor\":\"col2\"},\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filters\":[]},\"references\":[{\"type\":\"index-pattern\",\"id\":\"90943e30-9a47-11e8-b64d-95841ca0b247\",\"name\":\"indexpattern-datasource-current-indexpattern\"},{\"type\":\"index-pattern\",\"id\":\"90943e30-9a47-11e8-b64d-95841ca0b247\",\"name\":\"indexpattern-datasource-layer-layer1\"}]}}}\n\nbrbrbr" +`; + const caseComment = { type: 'cases-comments', id: '1cefd0d0-e86d-11eb-bae5-3d065cd16a32', attributes: { associationType: 'case', - comment: `Amaing\n\n**!!!**\n\n!{lens{"timeRange":{"from":"now-7d","to":"now","mode":"relative"},"editMode":false,"attributes":${JSON.stringify( + comment: `"**Amazing**\n\n!{tooltip[Tessss](https://regex101.com/r/qQ1rvs/1)}\n\nbrbrbr\n\n[asdasdasdasd](http://localhost:5601/moq/app/security/timelines?timeline=(id%3A%27e4362a60-f478-11eb-a4b0-ebefce184d8d%27%2CisOpen%3A!t))\n\n!{lens{\"timeRange\":{\"from\":\"now-7d\",\"to\":\"now\",\"mode\":\"relative\"},\"editMode\":false,\"attributes\":${JSON.stringify( lensVisualizationToMigrate - )}}}`, + )}}}\n\n!{lens{\"timeRange\":{\"from\":\"now-7d\",\"to\":\"now\",\"mode\":\"relative\"},\"editMode\":false,\"attributes\":{\"title\":\"TEst22\",\"type\":\"lens\",\"visualizationType\":\"lnsMetric\",\"state\":{\"datasourceStates\":{\"indexpattern\":{\"layers\":{\"layer1\":{\"columnOrder\":[\"col2\"],\"columns\":{\"col2\":{\"dataType\":\"number\",\"isBucketed\":false,\"label\":\"Count of records\",\"operationType\":\"count\",\"scale\":\"ratio\",\"sourceField\":\"Records\"}}}}}},\"visualization\":{\"layerId\":\"layer1\",\"accessor\":\"col2\"},\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filters\":[]},\"references\":[{\"type\":\"index-pattern\",\"id\":\"90943e30-9a47-11e8-b64d-95841ca0b247\",\"name\":\"indexpattern-datasource-current-indexpattern\"},{\"type\":\"index-pattern\",\"id\":\"90943e30-9a47-11e8-b64d-95841ca0b247\",\"name\":\"indexpattern-datasource-layer-layer1\"}]}}}\n\nbrbrbr"`, type: 'user', created_at: '2021-07-19T08:41:29.951Z', created_by: { @@ -152,6 +225,7 @@ describe('Comments migrations', () => { const layers = Object.values( lensVisualizations[0].attributes.state.datasourceStates.indexpattern.layers ); + expect(result.attributes.comment).toEqual(expectedMigrationCommentResult); expect(layers.length).toBe(1); const columns = Object.values(layers[0].columns); expect(columns.length).toBe(3); From b63776a0a4dfe77156734b5a76850c63f0dacc11 Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Sun, 8 Aug 2021 21:27:40 +0200 Subject: [PATCH 29/41] WIP --- .../utils/markdown_plugins/lens/constants.ts | 8 + .../utils/markdown_plugins/lens/index.ts | 10 + .../utils/markdown_plugins/lens/parser.ts} | 12 +- .../utils/markdown_plugins/lens/serializer.ts | 22 ++ .../utils/markdown_plugins/timeline/index.ts | 9 + .../utils/markdown_plugins/timeline/parser.ts | 83 ++++++++ .../markdown_plugins/timeline/serializer.ts | 15 ++ .../plugins/cases/server/common/utils.test.ts | 188 ++++++++++-------- x-pack/plugins/cases/server/common/utils.ts | 17 +- .../saved_object_types/migrations.test.ts | 4 +- x-pack/plugins/cases/tsconfig.json | 3 +- 11 files changed, 266 insertions(+), 105 deletions(-) create mode 100644 x-pack/plugins/cases/common/utils/markdown_plugins/lens/constants.ts create mode 100644 x-pack/plugins/cases/common/utils/markdown_plugins/lens/index.ts rename x-pack/plugins/cases/{server/common/lens_parser.ts => common/utils/markdown_plugins/lens/parser.ts} (89%) create mode 100644 x-pack/plugins/cases/common/utils/markdown_plugins/lens/serializer.ts create mode 100644 x-pack/plugins/cases/common/utils/markdown_plugins/timeline/index.ts create mode 100644 x-pack/plugins/cases/common/utils/markdown_plugins/timeline/parser.ts create mode 100644 x-pack/plugins/cases/common/utils/markdown_plugins/timeline/serializer.ts diff --git a/x-pack/plugins/cases/common/utils/markdown_plugins/lens/constants.ts b/x-pack/plugins/cases/common/utils/markdown_plugins/lens/constants.ts new file mode 100644 index 0000000000000..bc67e1b3228bb --- /dev/null +++ b/x-pack/plugins/cases/common/utils/markdown_plugins/lens/constants.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export const LENS_ID = 'lens'; diff --git a/x-pack/plugins/cases/common/utils/markdown_plugins/lens/index.ts b/x-pack/plugins/cases/common/utils/markdown_plugins/lens/index.ts new file mode 100644 index 0000000000000..4f48da5838380 --- /dev/null +++ b/x-pack/plugins/cases/common/utils/markdown_plugins/lens/index.ts @@ -0,0 +1,10 @@ +/* + * 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. + */ + +export * from './constants'; +export * from './parser'; +export * from './serializer'; diff --git a/x-pack/plugins/cases/server/common/lens_parser.ts b/x-pack/plugins/cases/common/utils/markdown_plugins/lens/parser.ts similarity index 89% rename from x-pack/plugins/cases/server/common/lens_parser.ts rename to x-pack/plugins/cases/common/utils/markdown_plugins/lens/parser.ts index 4846ad2bcb9de..58ebfd76d5ac5 100644 --- a/x-pack/plugins/cases/server/common/lens_parser.ts +++ b/x-pack/plugins/cases/common/utils/markdown_plugins/lens/parser.ts @@ -7,9 +7,7 @@ import { Plugin } from 'unified'; import { RemarkTokenizer } from '@elastic/eui'; -// import { ID } from './constants'; - -export const ID = 'lens'; +import { LENS_ID } from './constants'; export const LensParser: Plugin = function () { const Parser = this.Parser; @@ -17,7 +15,7 @@ export const LensParser: Plugin = function () { const methods = Parser.prototype.blockMethods; const tokenizeLens: RemarkTokenizer = function (eat, value, silent) { - if (value.startsWith(`!{${ID}`) === false) return true; + if (value.startsWith(`!{${LENS_ID}`) === false) return true; const nextChar = value[6]; @@ -30,7 +28,7 @@ export const LensParser: Plugin = function () { // is there a configuration? const hasConfiguration = nextChar === '{'; - let match = `!{${ID}`; + let match = `!{${LENS_ID}`; let configuration = {}; if (hasConfiguration) { @@ -69,11 +67,11 @@ export const LensParser: Plugin = function () { match += '}'; return eat(match)({ - type: ID, + type: LENS_ID, ...configuration, }); }; tokenizers.lens = tokenizeLens; - methods.splice(methods.indexOf('text'), 0, ID); + methods.splice(methods.indexOf('text'), 0, LENS_ID); }; diff --git a/x-pack/plugins/cases/common/utils/markdown_plugins/lens/serializer.ts b/x-pack/plugins/cases/common/utils/markdown_plugins/lens/serializer.ts new file mode 100644 index 0000000000000..3af4efe2e2aca --- /dev/null +++ b/x-pack/plugins/cases/common/utils/markdown_plugins/lens/serializer.ts @@ -0,0 +1,22 @@ +/* + * 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. + */ + +import type { TimeRange } from 'src/plugins/data/common'; +import { LENS_ID } from './constants'; + +export interface LensSerializerProps { + attributes: Record; + timeRange: TimeRange; + editMode: boolean | undefined; +} + +export const LensSerializer = ({ timeRange, editMode, attributes }: LensSerializerProps) => + `!{${LENS_ID}${JSON.stringify({ + timeRange, + editMode, + attributes, + })}}`; diff --git a/x-pack/plugins/cases/common/utils/markdown_plugins/timeline/index.ts b/x-pack/plugins/cases/common/utils/markdown_plugins/timeline/index.ts new file mode 100644 index 0000000000000..c6a22791db5f6 --- /dev/null +++ b/x-pack/plugins/cases/common/utils/markdown_plugins/timeline/index.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +export * from './parser'; +export * from './serializer'; diff --git a/x-pack/plugins/cases/common/utils/markdown_plugins/timeline/parser.ts b/x-pack/plugins/cases/common/utils/markdown_plugins/timeline/parser.ts new file mode 100644 index 0000000000000..e7fe1c9648d6d --- /dev/null +++ b/x-pack/plugins/cases/common/utils/markdown_plugins/timeline/parser.ts @@ -0,0 +1,83 @@ +/* + * 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. + */ + +import { Plugin } from 'unified'; +import { RemarkTokenizer } from '@elastic/eui'; + +export const ID = 'timeline'; +const PREFIX = '['; + +export const TimelineParser: Plugin = function () { + const Parser = this.Parser; + const tokenizers = Parser.prototype.blockTokenizers; + const methods = Parser.prototype.blockMethods; + + const tokenizeTimeline: RemarkTokenizer = function (eat, value, silent) { + if ( + value.startsWith(PREFIX) === false || + (value.startsWith(PREFIX) === true && !value.includes('timelines?timeline=(id')) + ) { + return false; + } + + let index = 0; + const nextChar = value[index]; + + if (nextChar !== PREFIX) { + return false; + } + + if (silent) { + return true; + } + + function readArg(open: string, close: string) { + if (value[index] !== open) { + throw new Error(i18n.NO_PARENTHESES); + } + + index++; + + let body = ''; + let openBrackets = 0; + + for (; index < value.length; index++) { + const char = value[index]; + + if (char === close && openBrackets === 0) { + index++; + return body; + } else if (char === close) { + openBrackets--; + } else if (char === open) { + openBrackets++; + } + + body += char; + } + + return ''; + } + + const timelineTitle = readArg(PREFIX, ']'); + const timelineUrl = readArg('(', ')'); + const now = eat.now(); + const match = `[${timelineTitle}](${timelineUrl})`; + + return eat(match)({ + type: ID, + match, + }); + }; + + tokenizeTimeline.locator = (value: string, fromIndex: number) => { + return value.indexOf(PREFIX, fromIndex); + }; + + tokenizers.timeline = tokenizeTimeline; + methods.splice(methods.indexOf('url'), 0, ID); +}; diff --git a/x-pack/plugins/cases/common/utils/markdown_plugins/timeline/serializer.ts b/x-pack/plugins/cases/common/utils/markdown_plugins/timeline/serializer.ts new file mode 100644 index 0000000000000..6767ba9bec8c0 --- /dev/null +++ b/x-pack/plugins/cases/common/utils/markdown_plugins/timeline/serializer.ts @@ -0,0 +1,15 @@ +/* + * 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. + */ + +import type { TimeRange } from 'src/plugins/data/common'; +import { ID } from './constants'; + +export interface TimelineSerializerProps { + match: string; +} + +export const TimelineSerializer = ({ match }: TimelineSerializerProps) => match; diff --git a/x-pack/plugins/cases/server/common/utils.test.ts b/x-pack/plugins/cases/server/common/utils.test.ts index dba9ff8d829a2..8c25e65fabc48 100644 --- a/x-pack/plugins/cases/server/common/utils.test.ts +++ b/x-pack/plugins/cases/server/common/utils.test.ts @@ -873,40 +873,18 @@ describe('common utils', () => { }); describe('extractLensReferencesFromCommentString', () => { - const commentString = [ - '**Test** ', - 'Amazingg!!!', - '!{lens{"timeRange":{"from":"now-7d","to":"now","mode":"relative"},"attributes":{"title":"aaaa","type":"lens","visualizationType":"lnsXY","state":{"datasourceStates":{"indexpattern":{"layers":{"layer1":{"columnOrder":["col1","col2"],"columns":{"col2":{"dataType":"number","isBucketed":false,"label":"Count of records","operationType":"count","scale":"ratio","sourceField":"Records"},"col1":{"dataType":"date","isBucketed":true,"label":"@timestamp","operationType":"date_histogram","params":{"interval":"auto"},"scale":"interval","sourceField":"timestamp"}}}}}},"visualization":{"axisTitlesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"fittingFunction":"None","gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"layers":[{"accessors":["col2"],"layerId":"layer1","seriesType":"bar_stacked","xAccessor":"col1","yConfig":[{"forAccessor":"col2"}]}],"legend":{"isVisible":true,"position":"right"},"preferredSeriesType":"bar_stacked","tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"valueLabels":"hide","yRightExtent":{"mode":"full"}},"query":{"language":"kuery","query":""},"filters":[]},"references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b246","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b248","name":"indexpattern-datasource-layer-layer1"}]},"editMode":false}}', - '!{lens{"timeRange":{"from":"now-7d","to":"now","mode":"relative"},"attributes":{"title":"aaaa","type":"lens","visualizationType":"lnsXY","state":{"datasourceStates":{"indexpattern":{"layers":{"layer1":{"columnOrder":["col1","col2"],"columns":{"col2":{"dataType":"number","isBucketed":false,"label":"Count of records","operationType":"count","scale":"ratio","sourceField":"Records"},"col1":{"dataType":"date","isBucketed":true,"label":"@timestamp","operationType":"date_histogram","params":{"interval":"auto"},"scale":"interval","sourceField":"timestamp"}}}}}},"visualization":{"axisTitlesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"fittingFunction":"None","gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"layers":[{"accessors":["col2"],"layerId":"layer1","seriesType":"bar_stacked","xAccessor":"col1","yConfig":[{"forAccessor":"col2"}]}],"legend":{"isVisible":true,"position":"right"},"preferredSeriesType":"bar_stacked","tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"valueLabels":"hide","yRightExtent":{"mode":"full"}},"query":{"language":"kuery","query":""},"filters":[]},"references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b246","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-layer1"}]},"editMode":false}}', - ].join('\n\n'); - - const extractedReferences = extractLensReferencesFromCommentString(commentString); - - const expectedReferences = [ - { - type: 'index-pattern', - id: '90943e30-9a47-11e8-b64d-95841ca0b246', - name: 'indexpattern-datasource-current-indexpattern', - }, - { - type: 'index-pattern', - id: '90943e30-9a47-11e8-b64d-95841ca0b248', - name: 'indexpattern-datasource-layer-layer1', - }, - { - type: 'index-pattern', - id: '90943e30-9a47-11e8-b64d-95841ca0b247', - name: 'indexpattern-datasource-layer-layer1', - }, - ]; - - expect(expectedReferences.length).toEqual(extractedReferences.length); - expect(expectedReferences).toEqual(expect.arrayContaining(extractedReferences)); - }); - - describe('getOrUpdateLensReferences', () => { - const currentCommentStringReferences = [ - [ + it('extracts successfully', () => { + const commentString = [ + '**Test** ', + 'Amazingg!!!', + '[asdasdasdasd](http://localhost:5601/moq/app/security/timelines?timeline=(id%3A%27e4362a60-f478-11eb-a4b0-ebefce184d8d%27%2CisOpen%3A!t))', + '!{lens{"timeRange":{"from":"now-7d","to":"now","mode":"relative"},"attributes":{"title":"aaaa","type":"lens","visualizationType":"lnsXY","state":{"datasourceStates":{"indexpattern":{"layers":{"layer1":{"columnOrder":["col1","col2"],"columns":{"col2":{"dataType":"number","isBucketed":false,"label":"Count of records","operationType":"count","scale":"ratio","sourceField":"Records"},"col1":{"dataType":"date","isBucketed":true,"label":"@timestamp","operationType":"date_histogram","params":{"interval":"auto"},"scale":"interval","sourceField":"timestamp"}}}}}},"visualization":{"axisTitlesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"fittingFunction":"None","gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"layers":[{"accessors":["col2"],"layerId":"layer1","seriesType":"bar_stacked","xAccessor":"col1","yConfig":[{"forAccessor":"col2"}]}],"legend":{"isVisible":true,"position":"right"},"preferredSeriesType":"bar_stacked","tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"valueLabels":"hide","yRightExtent":{"mode":"full"}},"query":{"language":"kuery","query":""},"filters":[]},"references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b246","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b248","name":"indexpattern-datasource-layer-layer1"}]},"editMode":false}}', + '!{lens{"timeRange":{"from":"now-7d","to":"now","mode":"relative"},"attributes":{"title":"aaaa","type":"lens","visualizationType":"lnsXY","state":{"datasourceStates":{"indexpattern":{"layers":{"layer1":{"columnOrder":["col1","col2"],"columns":{"col2":{"dataType":"number","isBucketed":false,"label":"Count of records","operationType":"count","scale":"ratio","sourceField":"Records"},"col1":{"dataType":"date","isBucketed":true,"label":"@timestamp","operationType":"date_histogram","params":{"interval":"auto"},"scale":"interval","sourceField":"timestamp"}}}}}},"visualization":{"axisTitlesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"fittingFunction":"None","gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"layers":[{"accessors":["col2"],"layerId":"layer1","seriesType":"bar_stacked","xAccessor":"col1","yConfig":[{"forAccessor":"col2"}]}],"legend":{"isVisible":true,"position":"right"},"preferredSeriesType":"bar_stacked","tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"valueLabels":"hide","yRightExtent":{"mode":"full"}},"query":{"language":"kuery","query":""},"filters":[]},"references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b246","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-layer1"}]},"editMode":false}}', + ].join('\n\n'); + + const extractedReferences = extractLensReferencesFromCommentString(commentString); + + const expectedReferences = [ { type: 'index-pattern', id: '90943e30-9a47-11e8-b64d-95841ca0b246', @@ -917,11 +895,72 @@ describe('common utils', () => { id: '90943e30-9a47-11e8-b64d-95841ca0b248', name: 'indexpattern-datasource-layer-layer1', }, - ], - [ { type: 'index-pattern', - id: '90943e30-9a47-11e8-b64d-95841ca0b246', + id: '90943e30-9a47-11e8-b64d-95841ca0b247', + name: 'indexpattern-datasource-layer-layer1', + }, + ]; + + expect(expectedReferences.length).toEqual(extractedReferences.length); + expect(expectedReferences).toEqual(expect.arrayContaining(extractedReferences)); + }); + }); + + describe('getOrUpdateLensReferences', () => { + it('update references', () => { + const currentCommentStringReferences = [ + [ + { + type: 'index-pattern', + id: '90943e30-9a47-11e8-b64d-95841ca0b246', + name: 'indexpattern-datasource-current-indexpattern', + }, + { + type: 'index-pattern', + id: '90943e30-9a47-11e8-b64d-95841ca0b248', + name: 'indexpattern-datasource-layer-layer1', + }, + ], + [ + { + type: 'index-pattern', + id: '90943e30-9a47-11e8-b64d-95841ca0b246', + name: 'indexpattern-datasource-current-indexpattern', + }, + { + type: 'index-pattern', + id: '90943e30-9a47-11e8-b64d-95841ca0b248', + name: 'indexpattern-datasource-layer-layer1', + }, + ], + ]; + const currentCommentString = [ + '**Test** ', + '[asdasdasdasd](http://localhost:5601/moq/app/security/timelines?timeline=(id%3A%27e4362a60-f478-11eb-a4b0-ebefce184d8d%27%2CisOpen%3A!t))', + `!{lens{"timeRange":{"from":"now-7d","to":"now","mode":"relative"},"attributes":{"title":"aaaa","type":"lens","visualizationType":"lnsXY","state":{"datasourceStates":{"indexpattern":{"layers":{"layer1":{"columnOrder":["col1","col2"],"columns":{"col2":{"dataType":"number","isBucketed":false,"label":"Count of records","operationType":"count","scale":"ratio","sourceField":"Records"},"col1":{"dataType":"date","isBucketed":true,"label":"@timestamp","operationType":"date_histogram","params":{"interval":"auto"},"scale":"interval","sourceField":"timestamp"}}}}}},"visualization":{"axisTitlesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"fittingFunction":"None","gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"layers":[{"accessors":["col2"],"layerId":"layer1","seriesType":"bar_stacked","xAccessor":"col1","yConfig":[{"forAccessor":"col2"}]}],"legend":{"isVisible":true,"position":"right"},"preferredSeriesType":"bar_stacked","tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"valueLabels":"hide","yRightExtent":{"mode":"full"}},"query":{"language":"kuery","query":""},"filters":[]},"references":${JSON.stringify( + currentCommentStringReferences[0] + )}},"editMode":false}}`, + `!{lens{"timeRange":{"from":"now-7d","to":"now","mode":"relative"},"attributes":{"title":"aaaa","type":"lens","visualizationType":"lnsXY","state":{"datasourceStates":{"indexpattern":{"layers":{"layer1":{"columnOrder":["col1","col2"],"columns":{"col2":{"dataType":"number","isBucketed":false,"label":"Count of records","operationType":"count","scale":"ratio","sourceField":"Records"},"col1":{"dataType":"date","isBucketed":true,"label":"@timestamp","operationType":"date_histogram","params":{"interval":"auto"},"scale":"interval","sourceField":"timestamp"}}}}}},"visualization":{"axisTitlesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"fittingFunction":"None","gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"layers":[{"accessors":["col2"],"layerId":"layer1","seriesType":"bar_stacked","xAccessor":"col1","yConfig":[{"forAccessor":"col2"}]}],"legend":{"isVisible":true,"position":"right"},"preferredSeriesType":"bar_stacked","tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"valueLabels":"hide","yRightExtent":{"mode":"full"}},"query":{"language":"kuery","query":""},"filters":[]},"references":${JSON.stringify( + currentCommentStringReferences[1] + )}},"editMode":false}}`, + ].join('\n\n'); + const nonLensCurrentCommentReferences = [ + { type: 'case', id: '7b4be181-9646-41b8-b12d-faabf1bd9512', name: 'Test case' }, + { + type: 'timeline', + id: '0f847d31-9683-4ebd-92b9-454e3e39aec1', + name: 'Test case timeline', + }, + ]; + const currentCommentReferences = [ + ...currentCommentStringReferences.flat(), + ...nonLensCurrentCommentReferences, + ]; + const newCommentStringReferences = [ + { + type: 'index-pattern', + id: '90943e30-9a47-11e8-b64d-95841ca0b245', name: 'indexpattern-datasource-current-indexpattern', }, { @@ -929,55 +968,30 @@ describe('common utils', () => { id: '90943e30-9a47-11e8-b64d-95841ca0b248', name: 'indexpattern-datasource-layer-layer1', }, - ], - ]; - const currentCommentString = [ - '**Test** ', - `!{lens{"timeRange":{"from":"now-7d","to":"now","mode":"relative"},"attributes":{"title":"aaaa","type":"lens","visualizationType":"lnsXY","state":{"datasourceStates":{"indexpattern":{"layers":{"layer1":{"columnOrder":["col1","col2"],"columns":{"col2":{"dataType":"number","isBucketed":false,"label":"Count of records","operationType":"count","scale":"ratio","sourceField":"Records"},"col1":{"dataType":"date","isBucketed":true,"label":"@timestamp","operationType":"date_histogram","params":{"interval":"auto"},"scale":"interval","sourceField":"timestamp"}}}}}},"visualization":{"axisTitlesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"fittingFunction":"None","gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"layers":[{"accessors":["col2"],"layerId":"layer1","seriesType":"bar_stacked","xAccessor":"col1","yConfig":[{"forAccessor":"col2"}]}],"legend":{"isVisible":true,"position":"right"},"preferredSeriesType":"bar_stacked","tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"valueLabels":"hide","yRightExtent":{"mode":"full"}},"query":{"language":"kuery","query":""},"filters":[]},"references":${JSON.stringify( - currentCommentStringReferences[0] - )}},"editMode":false}}`, - `!{lens{"timeRange":{"from":"now-7d","to":"now","mode":"relative"},"attributes":{"title":"aaaa","type":"lens","visualizationType":"lnsXY","state":{"datasourceStates":{"indexpattern":{"layers":{"layer1":{"columnOrder":["col1","col2"],"columns":{"col2":{"dataType":"number","isBucketed":false,"label":"Count of records","operationType":"count","scale":"ratio","sourceField":"Records"},"col1":{"dataType":"date","isBucketed":true,"label":"@timestamp","operationType":"date_histogram","params":{"interval":"auto"},"scale":"interval","sourceField":"timestamp"}}}}}},"visualization":{"axisTitlesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"fittingFunction":"None","gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"layers":[{"accessors":["col2"],"layerId":"layer1","seriesType":"bar_stacked","xAccessor":"col1","yConfig":[{"forAccessor":"col2"}]}],"legend":{"isVisible":true,"position":"right"},"preferredSeriesType":"bar_stacked","tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"valueLabels":"hide","yRightExtent":{"mode":"full"}},"query":{"language":"kuery","query":""},"filters":[]},"references":${JSON.stringify( - currentCommentStringReferences[1] - )}},"editMode":false}}`, - ].join('\n\n'); - const nonLensCurrentCommentReferences = [ - { type: 'case', id: '7b4be181-9646-41b8-b12d-faabf1bd9512', name: 'Test case' }, - { type: 'timeline', id: '0f847d31-9683-4ebd-92b9-454e3e39aec1', name: 'Test case timeline' }, - ]; - const currentCommentReferences = [ - ...currentCommentStringReferences.flat(), - ...nonLensCurrentCommentReferences, - ]; - const newCommentStringReferences = [ - { - type: 'index-pattern', - id: '90943e30-9a47-11e8-b64d-95841ca0b245', - name: 'indexpattern-datasource-current-indexpattern', - }, - { - type: 'index-pattern', - id: '90943e30-9a47-11e8-b64d-95841ca0b248', - name: 'indexpattern-datasource-layer-layer1', - }, - ]; - const newCommentString = [ - '**Test** ', - 'Awmazingg!!!', - `!{lens{"timeRange":{"from":"now-7d","to":"now","mode":"relative"},"attributes":{"title":"aaaa","type":"lens","visualizationType":"lnsXY","state":{"datasourceStates":{"indexpattern":{"layers":{"layer1":{"columnOrder":["col1","col2"],"columns":{"col2":{"dataType":"number","isBucketed":false,"label":"Count of records","operationType":"count","scale":"ratio","sourceField":"Records"},"col1":{"dataType":"date","isBucketed":true,"label":"@timestamp","operationType":"date_histogram","params":{"interval":"auto"},"scale":"interval","sourceField":"timestamp"}}}}}},"visualization":{"axisTitlesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"fittingFunction":"None","gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"layers":[{"accessors":["col2"],"layerId":"layer1","seriesType":"bar_stacked","xAccessor":"col1","yConfig":[{"forAccessor":"col2"}]}],"legend":{"isVisible":true,"position":"right"},"preferredSeriesType":"bar_stacked","tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"valueLabels":"hide","yRightExtent":{"mode":"full"}},"query":{"language":"kuery","query":""},"filters":[]},"references":${JSON.stringify( - newCommentStringReferences - )}},"editMode":false}}`, - ].join('\n\n'); - - const updatedReferences = getOrUpdateLensReferences(newCommentString, { - references: currentCommentReferences, - attributes: { - comment: currentCommentString, - }, - } as SavedObject); - - const expectedReferences = [...nonLensCurrentCommentReferences, ...newCommentStringReferences]; - - expect(expectedReferences.length).toEqual(updatedReferences.length); - expect(expectedReferences).toEqual(expect.arrayContaining(updatedReferences)); + ]; + const newCommentString = [ + '**Test** ', + 'Awmazingg!!!', + '[asdasdasdasd](http://localhost:5601/moq/app/security/timelines?timeline=(id%3A%27e4362a60-f478-11eb-a4b0-ebefce184d8d%27%2CisOpen%3A!t))', + `!{lens{"timeRange":{"from":"now-7d","to":"now","mode":"relative"},"attributes":{"title":"aaaa","type":"lens","visualizationType":"lnsXY","state":{"datasourceStates":{"indexpattern":{"layers":{"layer1":{"columnOrder":["col1","col2"],"columns":{"col2":{"dataType":"number","isBucketed":false,"label":"Count of records","operationType":"count","scale":"ratio","sourceField":"Records"},"col1":{"dataType":"date","isBucketed":true,"label":"@timestamp","operationType":"date_histogram","params":{"interval":"auto"},"scale":"interval","sourceField":"timestamp"}}}}}},"visualization":{"axisTitlesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"fittingFunction":"None","gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"layers":[{"accessors":["col2"],"layerId":"layer1","seriesType":"bar_stacked","xAccessor":"col1","yConfig":[{"forAccessor":"col2"}]}],"legend":{"isVisible":true,"position":"right"},"preferredSeriesType":"bar_stacked","tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"valueLabels":"hide","yRightExtent":{"mode":"full"}},"query":{"language":"kuery","query":""},"filters":[]},"references":${JSON.stringify( + newCommentStringReferences + )}},"editMode":false}}`, + ].join('\n\n'); + + const updatedReferences = getOrUpdateLensReferences(newCommentString, { + references: currentCommentReferences, + attributes: { + comment: currentCommentString, + }, + } as SavedObject); + + const expectedReferences = [ + ...nonLensCurrentCommentReferences, + ...newCommentStringReferences, + ]; + + expect(expectedReferences.length).toEqual(updatedReferences.length); + expect(expectedReferences).toEqual(expect.arrayContaining(updatedReferences)); + }); }); }); diff --git a/x-pack/plugins/cases/server/common/utils.ts b/x-pack/plugins/cases/server/common/utils.ts index 75bf79cb61866..f0a53073a8f7e 100644 --- a/x-pack/plugins/cases/server/common/utils.ts +++ b/x-pack/plugins/cases/server/common/utils.ts @@ -49,7 +49,8 @@ import { User, } from '../../common'; import { UpdateAlertRequest } from '../client/alerts/types'; -import { LensParser, ID as LENS_ID } from './lens_parser'; +import { LENS_ID, LensParser, LensSerializer } from '../../common/utils/markdown_plugins/lens'; +import { TimelineSerializer, TimelineParser } from '../../common/utils/markdown_plugins/timeline'; /** * Default sort field for querying saved objects. @@ -453,7 +454,7 @@ interface LensMarkdownNode { } export const parseCommentString = (comment: string) => { - const processor = unified().use([[markdown, {}], LensParser]); + const processor = unified().use([[markdown, {}], LensParser, TimelineParser]); return processor.parse(comment) as Parent; }; @@ -465,13 +466,13 @@ export const stringifyComment = (comment: Parent) => { allowDangerousHtml: true, handlers: { + /* + because we're using rison in the timeline url we need + to make sure that markdown parser doesn't modify the url + */ + timeline: TimelineSerializer, // @ts-expect-error - lens: (a) => - `!{lens${JSON.stringify({ - timeRange: a.timeRange, - editMode: a.editMode, - attributes: a.attributes, - })}}`, + lens: LensSerializer, }, }, ], diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations.test.ts b/x-pack/plugins/cases/server/saved_object_types/migrations.test.ts index 19781392395a7..b33e12950b252 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations.test.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations.test.ts @@ -151,7 +151,7 @@ describe('Comments migrations', () => { }, }; - const expectedMigrationCommentResult = `"**Amazing**\n\n!{tooltip[Tessss](https://regex101.com/r/qQ1rvs/1)}\n\nbrbrbr\n\n[asdasdasdasd](http://localhost:5601/moq/app/security/timelines?timeline=\\(id%3A%27e4362a60-f478-11eb-a4b0-ebefce184d8d%27%2CisOpen%3A!t\\))\n\n!{lens{\"timeRange\":{\"from\":\"now-7d\",\"to\":\"now\",\"mode\":\"relative\"},\"editMode\":false,\"attributes\":${JSON.stringify( + const expectedMigrationCommentResult = `"**Amazing**\n\n!{tooltip[Tessss](https://example.com)}\n\nbrbrbr\n\n[asdasdasdasd](http://localhost:5601/moq/app/security/timelines?timeline=(id%3A%27e4362a60-f478-11eb-a4b0-ebefce184d8d%27%2CisOpen%3A!t))\n\n!{lens{\"timeRange\":{\"from\":\"now-7d\",\"to\":\"now\",\"mode\":\"relative\"},\"editMode\":false,\"attributes\":${JSON.stringify( expectedLensVisualizationMigrated )}}}\n\n!{lens{\"timeRange\":{\"from\":\"now-7d\",\"to\":\"now\",\"mode\":\"relative\"},\"editMode\":false,\"attributes\":{\"title\":\"TEst22\",\"type\":\"lens\",\"visualizationType\":\"lnsMetric\",\"state\":{\"datasourceStates\":{\"indexpattern\":{\"layers\":{\"layer1\":{\"columnOrder\":[\"col2\"],\"columns\":{\"col2\":{\"dataType\":\"number\",\"isBucketed\":false,\"label\":\"Count of records\",\"operationType\":\"count\",\"scale\":\"ratio\",\"sourceField\":\"Records\"}}}}}},\"visualization\":{\"layerId\":\"layer1\",\"accessor\":\"col2\"},\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filters\":[]},\"references\":[{\"type\":\"index-pattern\",\"id\":\"90943e30-9a47-11e8-b64d-95841ca0b247\",\"name\":\"indexpattern-datasource-current-indexpattern\"},{\"type\":\"index-pattern\",\"id\":\"90943e30-9a47-11e8-b64d-95841ca0b247\",\"name\":\"indexpattern-datasource-layer-layer1\"}]}}}\n\nbrbrbr" `; @@ -161,7 +161,7 @@ describe('Comments migrations', () => { id: '1cefd0d0-e86d-11eb-bae5-3d065cd16a32', attributes: { associationType: 'case', - comment: `"**Amazing**\n\n!{tooltip[Tessss](https://regex101.com/r/qQ1rvs/1)}\n\nbrbrbr\n\n[asdasdasdasd](http://localhost:5601/moq/app/security/timelines?timeline=(id%3A%27e4362a60-f478-11eb-a4b0-ebefce184d8d%27%2CisOpen%3A!t))\n\n!{lens{\"timeRange\":{\"from\":\"now-7d\",\"to\":\"now\",\"mode\":\"relative\"},\"editMode\":false,\"attributes\":${JSON.stringify( + comment: `"**Amazing**\n\n!{tooltip[Tessss](https://example.com)}\n\nbrbrbr\n\n[asdasdasdasd](http://localhost:5601/moq/app/security/timelines?timeline=(id%3A%27e4362a60-f478-11eb-a4b0-ebefce184d8d%27%2CisOpen%3A!t))\n\n!{lens{\"timeRange\":{\"from\":\"now-7d\",\"to\":\"now\",\"mode\":\"relative\"},\"editMode\":false,\"attributes\":${JSON.stringify( lensVisualizationToMigrate )}}}\n\n!{lens{\"timeRange\":{\"from\":\"now-7d\",\"to\":\"now\",\"mode\":\"relative\"},\"editMode\":false,\"attributes\":{\"title\":\"TEst22\",\"type\":\"lens\",\"visualizationType\":\"lnsMetric\",\"state\":{\"datasourceStates\":{\"indexpattern\":{\"layers\":{\"layer1\":{\"columnOrder\":[\"col2\"],\"columns\":{\"col2\":{\"dataType\":\"number\",\"isBucketed\":false,\"label\":\"Count of records\",\"operationType\":\"count\",\"scale\":\"ratio\",\"sourceField\":\"Records\"}}}}}},\"visualization\":{\"layerId\":\"layer1\",\"accessor\":\"col2\"},\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filters\":[]},\"references\":[{\"type\":\"index-pattern\",\"id\":\"90943e30-9a47-11e8-b64d-95841ca0b247\",\"name\":\"indexpattern-datasource-current-indexpattern\"},{\"type\":\"index-pattern\",\"id\":\"90943e30-9a47-11e8-b64d-95841ca0b247\",\"name\":\"indexpattern-datasource-layer-layer1\"}]}}}\n\nbrbrbr"`, type: 'user', diff --git a/x-pack/plugins/cases/tsconfig.json b/x-pack/plugins/cases/tsconfig.json index 8871031072747..fc84519b1a5ee 100644 --- a/x-pack/plugins/cases/tsconfig.json +++ b/x-pack/plugins/cases/tsconfig.json @@ -26,6 +26,7 @@ { "path": "../triggers_actions_ui/tsconfig.json"}, { "path": "../../../src/plugins/es_ui_shared/tsconfig.json" }, { "path": "../../../src/plugins/kibana_react/tsconfig.json" }, - { "path": "../../../src/plugins/kibana_utils/tsconfig.json" } + { "path": "../../../src/plugins/kibana_utils/tsconfig.json" }, + { "path": "../../../src/plugins/saved_objects/tsconfig.json" }, ] } From 2d343c82db12f3967693cdd240910e66811c840d Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Sun, 8 Aug 2021 22:12:57 +0200 Subject: [PATCH 30/41] fix tsconfig --- x-pack/plugins/cases/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/cases/tsconfig.json b/x-pack/plugins/cases/tsconfig.json index fc84519b1a5ee..d731f4fa0e61c 100644 --- a/x-pack/plugins/cases/tsconfig.json +++ b/x-pack/plugins/cases/tsconfig.json @@ -27,6 +27,6 @@ { "path": "../../../src/plugins/es_ui_shared/tsconfig.json" }, { "path": "../../../src/plugins/kibana_react/tsconfig.json" }, { "path": "../../../src/plugins/kibana_utils/tsconfig.json" }, - { "path": "../../../src/plugins/saved_objects/tsconfig.json" }, + { "path": "../../../src/plugins/saved_objects/tsconfig.json" } ] } From 13c4d9845281a0f657cd62e02d8e14e51391de13 Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Mon, 9 Aug 2021 00:36:07 +0200 Subject: [PATCH 31/41] Cleanup --- .../utils/markdown_plugins/timeline/parser.ts | 2 +- .../markdown_plugins/timeline/serializer.ts | 3 - .../markdown_plugins/timeline/translations.ts | 15 ++++ .../public/components/create/description.tsx | 43 ++++++--- .../components/markdown_editor/eui_form.tsx | 2 + .../markdown_editor/plugins/lens/index.ts | 3 +- .../plugins/lens/translations.ts | 15 ++++ .../plugins/lens/use_lens_draft_comment.ts | 65 ++++++++++++++ .../components/user_action_tree/index.tsx | 87 +++++-------------- .../user_action_tree/translations.ts | 7 -- x-pack/plugins/cases/server/common/utils.ts | 1 - 11 files changed, 154 insertions(+), 89 deletions(-) create mode 100644 x-pack/plugins/cases/common/utils/markdown_plugins/timeline/translations.ts create mode 100644 x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/translations.ts create mode 100644 x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/use_lens_draft_comment.ts diff --git a/x-pack/plugins/cases/common/utils/markdown_plugins/timeline/parser.ts b/x-pack/plugins/cases/common/utils/markdown_plugins/timeline/parser.ts index e7fe1c9648d6d..0decdae8c7348 100644 --- a/x-pack/plugins/cases/common/utils/markdown_plugins/timeline/parser.ts +++ b/x-pack/plugins/cases/common/utils/markdown_plugins/timeline/parser.ts @@ -7,6 +7,7 @@ import { Plugin } from 'unified'; import { RemarkTokenizer } from '@elastic/eui'; +import * as i18n from './translations'; export const ID = 'timeline'; const PREFIX = '['; @@ -65,7 +66,6 @@ export const TimelineParser: Plugin = function () { const timelineTitle = readArg(PREFIX, ']'); const timelineUrl = readArg('(', ')'); - const now = eat.now(); const match = `[${timelineTitle}](${timelineUrl})`; return eat(match)({ diff --git a/x-pack/plugins/cases/common/utils/markdown_plugins/timeline/serializer.ts b/x-pack/plugins/cases/common/utils/markdown_plugins/timeline/serializer.ts index 6767ba9bec8c0..0a95c9466b1ff 100644 --- a/x-pack/plugins/cases/common/utils/markdown_plugins/timeline/serializer.ts +++ b/x-pack/plugins/cases/common/utils/markdown_plugins/timeline/serializer.ts @@ -5,9 +5,6 @@ * 2.0. */ -import type { TimeRange } from 'src/plugins/data/common'; -import { ID } from './constants'; - export interface TimelineSerializerProps { match: string; } diff --git a/x-pack/plugins/cases/common/utils/markdown_plugins/timeline/translations.ts b/x-pack/plugins/cases/common/utils/markdown_plugins/timeline/translations.ts new file mode 100644 index 0000000000000..a1244f0ae67aa --- /dev/null +++ b/x-pack/plugins/cases/common/utils/markdown_plugins/timeline/translations.ts @@ -0,0 +1,15 @@ +/* + * 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. + */ + +import { i18n } from '@kbn/i18n'; + +export const NO_PARENTHESES = i18n.translate( + 'xpack.cases.markdownEditor.plugins.timeline.noParenthesesErrorMsg', + { + defaultMessage: 'Expected left parentheses', + } +); diff --git a/x-pack/plugins/cases/public/components/create/description.tsx b/x-pack/plugins/cases/public/components/create/description.tsx index 0a7102cff1ad5..d11c64789c3f0 100644 --- a/x-pack/plugins/cases/public/components/create/description.tsx +++ b/x-pack/plugins/cases/public/components/create/description.tsx @@ -5,26 +5,43 @@ * 2.0. */ -import React, { memo } from 'react'; +import React, { memo, useEffect, useRef } from 'react'; import { MarkdownEditorForm } from '../markdown_editor'; -import { UseField } from '../../common/shared_imports'; +import { UseField, useFormContext } from '../../common/shared_imports'; +import { useLensDraftComment } from '../markdown_editor/plugins/lens/use_lens_draft_comment'; + interface Props { isLoading: boolean; } export const fieldName = 'description'; -const DescriptionComponent: React.FC = ({ isLoading }) => ( - -); +const DescriptionComponent: React.FC = ({ isLoading }) => { + const { draftComment, openLensModal } = useLensDraftComment(); + const { setFieldValue } = useFormContext(); + const editorRef = useRef>(); + + useEffect(() => { + if (draftComment?.commentId === fieldName && editorRef.current) { + setFieldValue(fieldName, draftComment.comment); + openLensModal({ editorRef: editorRef.current }); + } + }, [draftComment, openLensModal, setFieldValue]); + + return ( + + ); +}; DescriptionComponent.displayName = 'DescriptionComponent'; diff --git a/x-pack/plugins/cases/public/components/markdown_editor/eui_form.tsx b/x-pack/plugins/cases/public/components/markdown_editor/eui_form.tsx index 2719f38f98fc2..d97618abde0c7 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/eui_form.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/eui_form.tsx @@ -31,6 +31,8 @@ export const MarkdownEditorForm = React.memo( ({ id, field, dataTestSubj, idAria, bottomRightContent }, ref) => { const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); + console.error(field, 'dupa'); + return ( <> { + const { + application: { currentAppId$ }, + embeddable, + storage, + } = useKibana().services; + const [draftComment, setDraftComment] = useState(null); + + useEffect(() => { + const fetchDraftComment = async () => { + const currentAppId = await currentAppId$.pipe(first()).toPromise(); + + if (!currentAppId) { + return; + } + + const incomingEmbeddablePackage = embeddable + ?.getStateTransfer() + .getIncomingEmbeddablePackage(currentAppId); + + if (incomingEmbeddablePackage) { + if (storage.get(DRAFT_COMMENT_STORAGE_ID)) { + try { + setDraftComment(JSON.parse(storage.get(DRAFT_COMMENT_STORAGE_ID))); + // eslint-disable-next-line no-empty + } catch (e) {} + } + } + }; + fetchDraftComment(); + }, [currentAppId$, embeddable, storage]); + + const openLensModal = useCallback( + ({ editorRef }) => { + if (editorRef && editorRef.textarea && editorRef.toolbar) { + const lensPluginButton = editorRef.toolbar?.querySelector(`[aria-label="${INSERT_LENS}"]`); + if (lensPluginButton) { + lensPluginButton.click(); + storage.remove(DRAFT_COMMENT_STORAGE_ID); + } + } + }, + [storage] + ); + + return { draftComment, openLensModal }; +}; diff --git a/x-pack/plugins/cases/public/components/user_action_tree/index.tsx b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx index ff7334bc9bb3d..b7834585e7423 100644 --- a/x-pack/plugins/cases/public/components/user_action_tree/index.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx @@ -5,7 +5,6 @@ * 2.0. */ -import { first } from 'rxjs/operators'; import { EuiFlexGroup, EuiFlexItem, @@ -23,7 +22,7 @@ import { isRight } from 'fp-ts/Either'; import * as i18n from './translations'; import { useUpdateComment } from '../../containers/use_update_comment'; -import { useCurrentUser, useKibana } from '../../common/lib/kibana'; +import { useCurrentUser } from '../../common/lib/kibana'; import { AddComment } from '../add_comment'; import { ActionConnector, @@ -56,7 +55,7 @@ import { UserActionTimestamp } from './user_action_timestamp'; import { UserActionUsername } from './user_action_username'; import { UserActionContentToolbar } from './user_action_content_toolbar'; import { getManualAlertIdsWithNoRuleId } from '../case_view/helpers'; -import { DRAFT_COMMENT_STORAGE_ID } from './constants'; +import { useLensDraftComment } from '../markdown_editor/plugins/lens/use_lens_draft_comment'; export interface UserActionTreeProps { caseServices: CaseServices; @@ -79,11 +78,6 @@ export interface UserActionTreeProps { userCanCrud: boolean; } -interface DraftComment { - commentId: string; - comment: string; -} - const MyEuiFlexGroup = styled(EuiFlexGroup)` margin-bottom: 8px; `; @@ -156,12 +150,6 @@ export const UserActionTree = React.memo( useFetchAlertData, userCanCrud, }: UserActionTreeProps) => { - const commentRefs = useRef>({}); - const { - application: { currentAppId$ }, - embeddable, - storage, - } = useKibana().services; const { detailName: caseId, commentId, subCaseId } = useParams<{ detailName: string; commentId?: string; @@ -173,6 +161,8 @@ export const UserActionTree = React.memo( const { isLoadingIds, patchComment } = useUpdateComment(); const currentUser = useCurrentUser(); const [manageMarkdownEditIds, setManageMarkdownEditIds] = useState([]); + const commentRefs = useRef>({}); + const { draftComment, openLensModal } = useLensDraftComment(); const [loadingAlertData, manualAlertsData] = useFetchAlertData( getManualAlertIdsWithNoRuleId(caseData.comments) @@ -642,57 +632,28 @@ export const UserActionTree = React.memo( const comments = [...userActions, ...bottomActions]; useEffect(() => { - const setInitialLensComment = async () => { - const currentAppId = await currentAppId$.pipe(first()).toPromise(); - - if (!currentAppId) { - return; - } - - const incomingEmbeddablePackage = embeddable - ?.getStateTransfer() - .getIncomingEmbeddablePackage(currentAppId); - - if (incomingEmbeddablePackage) { - let draftComment: DraftComment; - if (storage.get(DRAFT_COMMENT_STORAGE_ID)) { - try { - draftComment = JSON.parse(storage.get(DRAFT_COMMENT_STORAGE_ID)); - - if (draftComment?.commentId) { - setManageMarkdownEditIds((prevManageMarkdownEditIds) => { - if ( - ![NEW_ID, DESCRIPTION_ID].includes(draftComment?.commentId) && - !prevManageMarkdownEditIds.includes(draftComment?.commentId) - ) { - return [draftComment?.commentId]; - } - return prevManageMarkdownEditIds; - }); - - if ( - commentRefs.current && - commentRefs.current[draftComment.commentId] && - commentRefs.current[draftComment.commentId].editor?.textarea && - commentRefs.current[draftComment.commentId].editor?.toolbar - ) { - commentRefs.current[draftComment.commentId].setComment(draftComment.comment); - const lensPluginButton = commentRefs.current[ - draftComment.commentId - ].editor?.toolbar?.querySelector(`[aria-label="${i18n.INSERT_LENS}"]`); - if (lensPluginButton) { - lensPluginButton.click(); - storage.remove(DRAFT_COMMENT_STORAGE_ID); - } - } - } - // eslint-disable-next-line no-empty - } catch (e) {} + if (draftComment?.commentId) { + setManageMarkdownEditIds((prevManageMarkdownEditIds) => { + if ( + ![NEW_ID].includes(draftComment?.commentId) && + !prevManageMarkdownEditIds.includes(draftComment?.commentId) + ) { + return [draftComment?.commentId]; } + return prevManageMarkdownEditIds; + }); + + if ( + commentRefs.current && + commentRefs.current[draftComment.commentId] && + commentRefs.current[draftComment.commentId].editor?.textarea && + commentRefs.current[draftComment.commentId].editor?.toolbar + ) { + commentRefs.current[draftComment.commentId].setComment(draftComment.comment); + openLensModal({ editorRef: commentRefs.current[draftComment.commentId].editor }); } - }; - setInitialLensComment(); - }, [currentAppId$, embeddable, manageMarkdownEditIds, storage]); + } + }, [draftComment, openLensModal]); return ( <> diff --git a/x-pack/plugins/cases/public/components/user_action_tree/translations.ts b/x-pack/plugins/cases/public/components/user_action_tree/translations.ts index c9c9baac1fe66..40d6fc5bc2ad8 100644 --- a/x-pack/plugins/cases/public/components/user_action_tree/translations.ts +++ b/x-pack/plugins/cases/public/components/user_action_tree/translations.ts @@ -57,13 +57,6 @@ export const UNKNOWN_RULE = i18n.translate('xpack.cases.caseView.unknownRule.lab defaultMessage: 'Unknown rule', }); -export const INSERT_LENS = i18n.translate( - 'xpack.cases.markdownEditor.plugins.lens.insertLensButtonLabel', - { - defaultMessage: 'Insert visualization', - } -); - export const ISOLATED_HOST = i18n.translate('xpack.cases.caseView.isolatedHost', { defaultMessage: 'submitted isolate request on host', }); diff --git a/x-pack/plugins/cases/server/common/utils.ts b/x-pack/plugins/cases/server/common/utils.ts index f0a53073a8f7e..6af33b0a34786 100644 --- a/x-pack/plugins/cases/server/common/utils.ts +++ b/x-pack/plugins/cases/server/common/utils.ts @@ -471,7 +471,6 @@ export const stringifyComment = (comment: Parent) => to make sure that markdown parser doesn't modify the url */ timeline: TimelineSerializer, - // @ts-expect-error lens: LensSerializer, }, }, From b119cdc0413850e4d514b7183579208fd211867f Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Mon, 9 Aug 2021 00:48:27 +0200 Subject: [PATCH 32/41] cleanup --- .../cases/public/components/markdown_editor/eui_form.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/x-pack/plugins/cases/public/components/markdown_editor/eui_form.tsx b/x-pack/plugins/cases/public/components/markdown_editor/eui_form.tsx index d97618abde0c7..2719f38f98fc2 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/eui_form.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/eui_form.tsx @@ -31,8 +31,6 @@ export const MarkdownEditorForm = React.memo( ({ id, field, dataTestSubj, idAria, bottomRightContent }, ref) => { const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); - console.error(field, 'dupa'); - return ( <> Date: Mon, 9 Aug 2021 01:01:03 +0200 Subject: [PATCH 33/41] cleanup --- .../markdown_editor/plugins/lens/helpers.ts | 90 ------------------- .../markdown_editor/plugins/lens/plugin.tsx | 18 ++-- x-pack/plugins/lens/public/plugin.ts | 6 +- 3 files changed, 10 insertions(+), 104 deletions(-) delete mode 100644 x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/helpers.ts diff --git a/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/helpers.ts b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/helpers.ts deleted file mode 100644 index f4549c246e39c..0000000000000 --- a/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/helpers.ts +++ /dev/null @@ -1,90 +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. - */ - -import type { IndexPattern } from 'src/plugins/data/public'; -import type { - TypedLensByValueInput, - PersistedIndexPatternLayer, - XYState, -} from '../../../../../../lens/public'; - -// Generate a Lens state based on some app-specific input parameters. -// `TypedLensByValueInput` can be used for type-safety - it uses the same interfaces as Lens-internal code. -export const getLensAttributes = ( - defaultIndexPattern: IndexPattern | null -): TypedLensByValueInput['attributes'] => { - const dataLayer: PersistedIndexPatternLayer = { - columnOrder: ['col1', 'col2'], - columns: { - col2: { - dataType: 'number', - isBucketed: false, - label: 'Count of records', - operationType: 'count', - scale: 'ratio', - sourceField: 'Records', - }, - col1: { - dataType: 'date', - isBucketed: true, - label: '@timestamp', - operationType: 'date_histogram', - params: { interval: 'auto' }, - scale: 'interval', - sourceField: defaultIndexPattern?.timeFieldName!, - }, - }, - }; - - const xyConfig: XYState = { - axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true }, - fittingFunction: 'None', - gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true }, - layers: [ - { - accessors: ['col2'], - layerId: 'layer1', - seriesType: 'bar_stacked', - xAccessor: 'col1', - yConfig: [{ forAccessor: 'col2', color: undefined }], - }, - ], - legend: { isVisible: true, position: 'right' }, - preferredSeriesType: 'bar_stacked', - tickLabelsVisibilitySettings: { x: true, yLeft: true, yRight: true }, - valueLabels: 'hide', - }; - - return { - visualizationType: 'lnsXY', - title: '', - references: [ - { - id: defaultIndexPattern?.id!, - name: 'indexpattern-datasource-current-indexpattern', - type: 'index-pattern', - }, - { - id: defaultIndexPattern?.id!, - name: 'indexpattern-datasource-layer-layer1', - type: 'index-pattern', - }, - ], - state: { - datasourceStates: { - indexpattern: { - layers: { - layer1: dataLayer, - }, - }, - }, - filters: [], - query: { language: 'kuery', query: '' }, - visualization: xyConfig, - }, - }; -}; diff --git a/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx index 67fe0de3270a9..270489be2d0ab 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx @@ -35,7 +35,6 @@ import { DRAFT_COMMENT_STORAGE_ID, ID } from './constants'; import { CommentEditorContext } from '../../context'; import { LensSavedObjectsModal } from './lens_saved_objects_modal'; import { ModalContainer } from './modal_container'; -import { getLensAttributes } from './helpers'; import type { EmbeddablePackageState, EmbeddableInput, @@ -69,7 +68,6 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ lens, storage, data: { - indexPatterns, query: { timefilter: { timefilter }, }, @@ -186,14 +184,13 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ ); lens?.navigateToPrefilledEditor( - { - id: '', - timeRange, - attributes: - lensAttributes ?? - lensEmbeddableAttributes ?? - getLensAttributes(await indexPatterns.getDefault()), - }, + lensAttributes || lensEmbeddableAttributes + ? { + id: '', + timeRange, + attributes: lensAttributes ?? lensEmbeddableAttributes, + } + : undefined, { originatingApp: currentAppId!, originatingPath, @@ -208,7 +205,6 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ lensEmbeddableAttributes, lens, timeRange, - indexPatterns, currentAppId, originatingPath, ] diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index b91209e01d2f7..18344302141a4 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -128,7 +128,7 @@ export interface LensPublicStart { * @experimental */ navigateToPrefilledEditor: ( - input: LensEmbeddableInput, + input: LensEmbeddableInput | undefined, options?: { openInNewTab?: boolean; originatingApp?: string; @@ -320,7 +320,7 @@ export class LensPlugin { { openInNewTab = false, originatingApp = '', originatingPath } = {} ) => { // for openInNewTab, we set the time range in url via getEditPath below - if (input.timeRange && !openInNewTab) { + if (input?.timeRange && !openInNewTab) { startDependencies.data.query.timefilter.timefilter.setTime(input.timeRange); } const transfer = new EmbeddableStateTransfer( @@ -329,7 +329,7 @@ export class LensPlugin { ); transfer.navigateToEditor(APP_ID, { openInNewTab, - path: getEditPath(undefined, openInNewTab ? input.timeRange : undefined), + path: getEditPath(undefined, (openInNewTab && input?.timeRange) || undefined), state: { originatingApp, originatingPath, From fec3cbd552bb034f9c29e4e6388fb38b458a8142 Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Mon, 9 Aug 2021 11:58:54 +0200 Subject: [PATCH 34/41] add mock --- .../cases/public/common/lib/kibana/kibana_react.mock.ts | 7 +++++++ .../cases/public/components/create/description.test.tsx | 1 + .../plugins/cases/public/components/create/form.test.tsx | 1 + .../plugins/lens/__mocks__/use_lens_draft_comment.ts | 8 ++++++++ 4 files changed, 17 insertions(+) create mode 100644 x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/__mocks__/use_lens_draft_comment.ts diff --git a/x-pack/plugins/cases/public/common/lib/kibana/kibana_react.mock.ts b/x-pack/plugins/cases/public/common/lib/kibana/kibana_react.mock.ts index ff03782447846..e1990efefeffc 100644 --- a/x-pack/plugins/cases/public/common/lib/kibana/kibana_react.mock.ts +++ b/x-pack/plugins/cases/public/common/lib/kibana/kibana_react.mock.ts @@ -15,6 +15,13 @@ import { EuiTheme } from '../../../../../../../src/plugins/kibana_react/common'; import { securityMock } from '../../../../../security/public/mocks'; import { triggersActionsUiMock } from '../../../../../triggers_actions_ui/public/mocks'; +export const mockCreateStartServicesMock = (): StartServices => + (({ + ...coreMock.createStart(), + security: securityMock.createStart(), + triggersActionsUi: triggersActionsUiMock.createStart(), + } as unknown) as StartServices); + export const createStartServicesMock = (): StartServices => (({ ...coreMock.createStart(), diff --git a/x-pack/plugins/cases/public/components/create/description.test.tsx b/x-pack/plugins/cases/public/components/create/description.test.tsx index fcd1f82d64a53..923c73193f992 100644 --- a/x-pack/plugins/cases/public/components/create/description.test.tsx +++ b/x-pack/plugins/cases/public/components/create/description.test.tsx @@ -12,6 +12,7 @@ import { act } from '@testing-library/react'; import { useForm, Form, FormHook } from '../../common/shared_imports'; import { Description } from './description'; import { schema, FormProps } from './schema'; +jest.mock('../markdown_editor/plugins/lens/use_lens_draft_comment'); describe('Description', () => { let globalForm: FormHook; diff --git a/x-pack/plugins/cases/public/components/create/form.test.tsx b/x-pack/plugins/cases/public/components/create/form.test.tsx index 783ead9b271fd..9c3071fe27ee5 100644 --- a/x-pack/plugins/cases/public/components/create/form.test.tsx +++ b/x-pack/plugins/cases/public/components/create/form.test.tsx @@ -23,6 +23,7 @@ import { useCaseConfigureResponse } from '../configure_cases/__mock__'; jest.mock('../../containers/use_get_tags'); jest.mock('../../containers/configure/use_connectors'); jest.mock('../../containers/configure/use_configure'); +jest.mock('../markdown_editor/plugins/lens/use_lens_draft_comment'); const useGetTagsMock = useGetTags as jest.Mock; const useConnectorsMock = useConnectors as jest.Mock; diff --git a/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/__mocks__/use_lens_draft_comment.ts b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/__mocks__/use_lens_draft_comment.ts new file mode 100644 index 0000000000000..a0f0d49b211fb --- /dev/null +++ b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/__mocks__/use_lens_draft_comment.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export const useLensDraftComment = () => ({}); From a72d87526dc4c3e1f9254bab978ed2b730bdda24 Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Mon, 9 Aug 2021 13:54:00 +0200 Subject: [PATCH 35/41] imports --- x-pack/plugins/cases/server/common/utils.ts | 2 +- .../server/saved_object_types/migrations/index.test.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/cases/server/common/utils.ts b/x-pack/plugins/cases/server/common/utils.ts index 5450c1c9e9465..ec4cc07810002 100644 --- a/x-pack/plugins/cases/server/common/utils.ts +++ b/x-pack/plugins/cases/server/common/utils.ts @@ -408,7 +408,7 @@ export function checkEnabledCaseConnectorOrThrow(subCaseID: string | undefined) * * @returns the 'none' connector */ - export const getNoneCaseConnector = () => ({ +export const getNoneCaseConnector = () => ({ id: 'none', name: 'none', type: ConnectorTypes.none, diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/index.test.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/index.test.ts index b33e12950b252..cc3a4cc01446a 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations/index.test.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/index.test.ts @@ -8,10 +8,10 @@ import { LensDocShape, migrations as lensMigrations, -} from '../../../lens/server/migrations/saved_object_migrations'; -import { createCommentsMigrations } from './migrations'; +} from '../../../../lens/server/migrations/saved_object_migrations'; +import { createCommentsMigrations } from './index'; import { SavedObjectMigrationFn } from 'src/core/server'; -import { getLensVisualizations, parseCommentString } from '../common'; +import { getLensVisualizations, parseCommentString } from '../../common'; describe('Comments migrations', () => { describe('7.14.0 remove time zone from Lens visualization date histogram', () => { From d11997bfcd7f5e7bfa9fb7fb7cb99582cec2f2ee Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Mon, 9 Aug 2021 16:41:36 +0200 Subject: [PATCH 36/41] cleanup --- .../cases/server/saved_object_types/migrations/index.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/index.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/index.ts index 2b5ccda11628c..258dd31367765 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations/index.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/index.ts @@ -138,12 +138,10 @@ export interface CreateCommentsMigrationsDeps { } export const createCommentsMigrations = (migrationDeps: CreateCommentsMigrationsDeps) => { - // @ts-expect-error - const lensMigrations = mapValues( - // @ts-expect-error + const lensMigrations = (mapValues( migrationDeps.getLensMigrations(), migrateByValueLensVisualizations - ) as MigrateFunctionsObject; + ) as unknown) as MigrateFunctionsObject; const commentsMigrations = { '7.11.0': flow( From af3fbe8a8ca265e36afffff0b6b635380339db48 Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Fri, 13 Aug 2021 17:24:42 +0200 Subject: [PATCH 37/41] UX improvements --- .../plugins/lens/lens_saved_objects_modal.tsx | 90 ------ .../markdown_editor/plugins/lens/plugin.tsx | 269 +++++++++++------- 2 files changed, 166 insertions(+), 193 deletions(-) delete mode 100644 x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx diff --git a/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx deleted file mode 100644 index 1fa23c01d68f5..0000000000000 --- a/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/lens_saved_objects_modal.tsx +++ /dev/null @@ -1,90 +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. - */ - -import { - EuiModal, - EuiModalProps, - EuiModalBody, - EuiModalHeader, - EuiModalHeaderTitle, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import React, { useMemo } from 'react'; - -import { - SavedObjectFinderUi, - SavedObjectFinderUiProps, -} from '../../../../../../../../src/plugins/saved_objects/public'; -import { useKibana } from '../../../../common/lib/kibana'; -import { ModalContainer } from './modal_container'; - -interface LensSavedObjectsModalProps { - onClose: EuiModalProps['onClose']; - onChoose: SavedObjectFinderUiProps['onChoose']; -} - -const LensSavedObjectsModalComponent: React.FC = ({ - onClose, - onChoose, -}) => { - const { savedObjects, uiSettings } = useKibana().services; - - const savedObjectMetaData = useMemo( - () => [ - { - type: 'lens', - getIconForSavedObject: () => 'lensApp', - name: i18n.translate( - 'xpack.cases.markdownEditor.plugins.lens.insertLensSavedObjectModal.searchSelection.savedObjectType.lens', - { - defaultMessage: 'Lens', - } - ), - includeFields: ['*'], - }, - ], - [] - ); - - return ( - - - - -

- -

-
-
- - - - } - savedObjectMetaData={savedObjectMetaData} - fixedPageSize={10} - uiSettings={uiSettings} - savedObjects={savedObjects} - /> - -
-
- ); -}; - -export const LensSavedObjectsModal = React.memo(LensSavedObjectsModalComponent); diff --git a/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx index 270489be2d0ab..d9451a3a37829 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx @@ -22,23 +22,36 @@ import { EuiFlexGroup, EuiFormRow, EuiMarkdownAstNodePosition, + EuiBetaBadge, } from '@elastic/eui'; -import React, { useCallback, useContext, useMemo, useEffect, useState } from 'react'; +import React, { ReactNode, useCallback, useContext, useMemo, useEffect, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { useLocation } from 'react-router-dom'; +import styled from 'styled-components'; import type { TypedLensByValueInput } from '../../../../../../lens/public'; import { useKibana } from '../../../../common/lib/kibana'; import { LensMarkDownRenderer } from './processor'; import { DRAFT_COMMENT_STORAGE_ID, ID } from './constants'; import { CommentEditorContext } from '../../context'; -import { LensSavedObjectsModal } from './lens_saved_objects_modal'; import { ModalContainer } from './modal_container'; import type { EmbeddablePackageState, EmbeddableInput, } from '../../../../../../../../src/plugins/embeddable/public'; +import { + SavedObjectFinderUi, + SavedObjectFinderUiProps, +} from '../../../../../../../../src/plugins/saved_objects/public'; + +const BetaBadgeWrapper = styled.span` + display: inline-flex; + + .euiToolTipAnchor { + display: inline-flex; + } +`; type LensIncomingEmbeddablePackage = Omit & { input: Omit & { @@ -56,6 +69,56 @@ type LensEuiMarkdownEditorUiPlugin = EuiMarkdownEditorUiPlugin<{ attributes: TypedLensByValueInput['attributes']; }>; +interface LensSavedObjectsPickerProps { + children: ReactNode; + onChoose: SavedObjectFinderUiProps['onChoose']; +} + +const LensSavedObjectsPickerComponent: React.FC = ({ + children, + onChoose, +}) => { + const { savedObjects, uiSettings } = useKibana().services; + + const savedObjectMetaData = useMemo( + () => [ + { + type: 'lens', + getIconForSavedObject: () => 'lensApp', + name: i18n.translate( + 'xpack.cases.markdownEditor.plugins.lens.insertLensSavedObjectModal.searchSelection.savedObjectType.lens', + { + defaultMessage: 'Lens', + } + ), + includeFields: ['*'], + }, + ], + [] + ); + + return ( + + } + savedObjectMetaData={savedObjectMetaData} + fixedPageSize={10} + uiSettings={uiSettings} + savedObjects={savedObjects} + children={children} + /> + ); +}; + +export const LensSavedObjectsPicker = React.memo(LensSavedObjectsPickerComponent); + const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ node, onCancel, @@ -73,7 +136,6 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ }, }, } = useKibana().services; - const [currentAppId, setCurrentAppId] = useState(undefined); const [editMode, setEditMode] = useState(!!node); @@ -87,7 +149,6 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ mode: 'relative', } ); - const [showLensSavedObjectsModal, setShowLensSavedObjectsModal] = useState(false); const commentEditorContext = useContext(CommentEditorContext); const markdownContext = useContext(EuiMarkdownContext); @@ -221,8 +282,6 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ [handleEditInLensClick] ); - const handleCloseLensSOModal = useCallback(() => setShowLensSavedObjectsModal(false), []); - useEffect(() => { if (node?.attributes) { setLensEmbeddableAttributes(node.attributes); @@ -272,58 +331,50 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ }, [embeddable, storage, timefilter, currentAppId]); return ( - <> - - - - {editMode ? ( - - ) : ( - - )} - - - - - - handleEditInLensClick()} - color="primary" - size="m" - iconType="lensApp" - fill - > + + + + + + {editMode ? ( - - - - setShowLensSavedObjectsModal(true)} - > + ) : ( - + )} + + + + + - {lensEmbeddableAttributes ? ( +
+
+ + {lensEmbeddableAttributes ? ( + <> - + - - - handleEditInLensClick()} - > - - - + + handleEditInLensClick()} + > + + - ) : null} - - - - - {'Cancel'} - {editMode ? ( - - - - ) : null} - - {editMode ? ( - - ) : ( - - )} + + + + ) : ( + + + handleEditInLensClick()} + color="primary" + size="m" + iconType="lensApp" + fill + > + + + + + )} + + + {'Cancel'} + {editMode ? ( + + - - - {showLensSavedObjectsModal ? ( - - ) : null} - + ) : null} + + {editMode ? ( + + ) : ( + + )} + + + ); }; From 6e15a15f0c17fd486f77c387c003d5dadbb9fd0f Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Mon, 16 Aug 2021 00:15:03 +0200 Subject: [PATCH 38/41] cleanup --- .../utils/markdown_plugins/lens/serializer.ts | 4 +- x-pack/plugins/cases/kibana.json | 1 + .../markdown_editor/plugins/lens/plugin.tsx | 118 +++++++----------- .../plugins/lens/use_lens_draft_comment.ts | 29 +++-- .../cases/server/client/attachments/add.ts | 9 ++ .../cases/server/client/attachments/update.ts | 7 ++ x-pack/plugins/cases/server/client/factory.ts | 3 + x-pack/plugins/cases/server/client/types.ts | 2 + .../server/common/models/commentable_case.ts | 16 ++- x-pack/plugins/cases/server/common/utils.ts | 23 ++-- x-pack/plugins/cases/server/plugin.ts | 12 +- .../server/saved_object_types/comments.ts | 10 +- .../migrations/index.test.ts | 4 +- .../saved_object_types/migrations/index.ts | 22 ++-- .../embeddable/lens_embeddable_factory.ts | 13 ++ x-pack/plugins/lens/server/plugin.tsx | 11 +- .../action_menu/action_menu.test.tsx | 59 --------- .../components/action_menu/action_menu.tsx | 94 -------------- .../exploratory_view/header/header.test.tsx | 2 +- 19 files changed, 163 insertions(+), 276 deletions(-) delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/components/action_menu/action_menu.test.tsx delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/components/action_menu/action_menu.tsx diff --git a/x-pack/plugins/cases/common/utils/markdown_plugins/lens/serializer.ts b/x-pack/plugins/cases/common/utils/markdown_plugins/lens/serializer.ts index 3af4efe2e2aca..e561b2f8cfb8a 100644 --- a/x-pack/plugins/cases/common/utils/markdown_plugins/lens/serializer.ts +++ b/x-pack/plugins/cases/common/utils/markdown_plugins/lens/serializer.ts @@ -11,12 +11,10 @@ import { LENS_ID } from './constants'; export interface LensSerializerProps { attributes: Record; timeRange: TimeRange; - editMode: boolean | undefined; } -export const LensSerializer = ({ timeRange, editMode, attributes }: LensSerializerProps) => +export const LensSerializer = ({ timeRange, attributes }: LensSerializerProps) => `!{${LENS_ID}${JSON.stringify({ timeRange, - editMode, attributes, })}}`; diff --git a/x-pack/plugins/cases/kibana.json b/x-pack/plugins/cases/kibana.json index ebac6295166df..13410895b1151 100644 --- a/x-pack/plugins/cases/kibana.json +++ b/x-pack/plugins/cases/kibana.json @@ -19,6 +19,7 @@ }, "requiredPlugins":[ "actions", + "embeddable", "esUiShared", "lens", "features", diff --git a/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx index d9451a3a37829..b27592c507628 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx @@ -36,14 +36,12 @@ import { LensMarkDownRenderer } from './processor'; import { DRAFT_COMMENT_STORAGE_ID, ID } from './constants'; import { CommentEditorContext } from '../../context'; import { ModalContainer } from './modal_container'; -import type { - EmbeddablePackageState, - EmbeddableInput, -} from '../../../../../../../../src/plugins/embeddable/public'; +import type { EmbeddablePackageState } from '../../../../../../../../src/plugins/embeddable/public'; import { SavedObjectFinderUi, SavedObjectFinderUiProps, } from '../../../../../../../../src/plugins/saved_objects/public'; +import { useLensDraftComment } from './use_lens_draft_comment'; const BetaBadgeWrapper = styled.span` display: inline-flex; @@ -54,10 +52,7 @@ const BetaBadgeWrapper = styled.span` `; type LensIncomingEmbeddablePackage = Omit & { - input: Omit & { - id: string | undefined; - attributes: TypedLensByValueInput['attributes']; - }; + input: TypedLensByValueInput; }; type LensEuiMarkdownEditorUiPlugin = EuiMarkdownEditorUiPlugin<{ @@ -138,10 +133,15 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ } = useKibana().services; const [currentAppId, setCurrentAppId] = useState(undefined); - const [editMode, setEditMode] = useState(!!node); + const { draftComment, clearDraftComment } = useLensDraftComment(); + + const [nodePosition, setNodePosition] = useState( + undefined + ); + // const [editMode, setEditMode] = useState(!!node); const [lensEmbeddableAttributes, setLensEmbeddableAttributes] = useState< TypedLensByValueInput['attributes'] | null - >(null); + >(node?.attributes || null); const [timeRange, setTimeRange] = useState( node?.timeRange ?? { from: 'now-7d', @@ -164,20 +164,11 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ }, []); const handleAdd = useCallback(() => { - let draftComment; - if (storage.get(DRAFT_COMMENT_STORAGE_ID)) { - try { - draftComment = JSON.parse(storage.get(DRAFT_COMMENT_STORAGE_ID)); - // eslint-disable-next-line no-empty - } catch (e) {} - } - - if (!node && draftComment?.position) { + if (nodePosition) { markdownContext.replaceNode( - draftComment.position, + nodePosition, `!{${ID}${JSON.stringify({ timeRange, - editMode, attributes: lensEmbeddableAttributes, })}}` ); @@ -190,7 +181,6 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ onSave( `!{${ID}${JSON.stringify({ timeRange, - editMode, attributes: lensEmbeddableAttributes, })}}`, { @@ -198,35 +188,14 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ } ); } - }, [ - storage, - node, - lensEmbeddableAttributes, - markdownContext, - timeRange, - editMode, - onCancel, - onSave, - ]); + }, [nodePosition, lensEmbeddableAttributes, markdownContext, timeRange, onCancel, onSave]); const handleDelete = useCallback(() => { - if (node?.position) { - markdownContext.replaceNode(node.position, ``); + if (nodePosition) { + markdownContext.replaceNode(nodePosition, ``); onCancel(); - return; } - - let draftComment; - if (storage.get(DRAFT_COMMENT_STORAGE_ID)) { - try { - draftComment = JSON.parse(storage.get(DRAFT_COMMENT_STORAGE_ID)); - markdownContext.replaceNode(draftComment?.position, ``); - onCancel(); - - // eslint-disable-next-line no-empty - } catch (e) {} - } - }, [markdownContext, node?.position, onCancel, storage]); + }, [markdownContext, nodePosition, onCancel]); const originatingPath = useMemo(() => `${location.pathname}${location.search}`, [ location.pathname, @@ -235,14 +204,12 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ const handleEditInLensClick = useCallback( async (lensAttributes?) => { - storage.set( - DRAFT_COMMENT_STORAGE_ID, - JSON.stringify({ - commentId: commentEditorContext?.editorId, - comment: commentEditorContext?.value, - position: node?.position, - }) - ); + storage.set(DRAFT_COMMENT_STORAGE_ID, { + commentId: commentEditorContext?.editorId, + comment: commentEditorContext?.value, + position: node?.position, + title: lensEmbeddableAttributes?.title, + }); lens?.navigateToPrefilledEditor( lensAttributes || lensEmbeddableAttributes @@ -263,8 +230,8 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ commentEditorContext?.editorId, commentEditorContext?.value, node?.position, - lensEmbeddableAttributes, lens, + lensEmbeddableAttributes, timeRange, currentAppId, originatingPath, @@ -282,12 +249,21 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ [handleEditInLensClick] ); + useEffect(() => () => clearDraftComment(), [clearDraftComment]); + useEffect(() => { if (node?.attributes) { setLensEmbeddableAttributes(node.attributes); } }, [node?.attributes]); + useEffect(() => { + const position = node?.position || draftComment?.position; + if (position) { + setNodePosition(position); + } + }, [node?.position, draftComment?.position]); + useEffect(() => { const getCurrentAppId = async () => { const appId = await currentAppId$.pipe(first()).toPromise(); @@ -309,7 +285,14 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ incomingEmbeddablePackage?.type === 'lens' && incomingEmbeddablePackage?.input?.attributes ) { - setLensEmbeddableAttributes(incomingEmbeddablePackage?.input.attributes); + const attributesTitle = incomingEmbeddablePackage?.input.attributes.title.length + ? incomingEmbeddablePackage?.input.attributes.title + : null; + setLensEmbeddableAttributes({ + ...incomingEmbeddablePackage?.input.attributes, + title: attributesTitle ?? draftComment?.title ?? '', + }); + const lensTime = timefilter.getTime(); if (lensTime?.from && lensTime?.to) { setTimeRange({ @@ -318,17 +301,8 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ mode: [lensTime.from, lensTime.to].join('').includes('now') ? 'relative' : 'absolute', }); } - - let draftComment; - if (storage.get(DRAFT_COMMENT_STORAGE_ID)) { - try { - draftComment = JSON.parse(storage.get(DRAFT_COMMENT_STORAGE_ID)); - setEditMode(!!draftComment?.editMode); - // eslint-disable-next-line no-empty - } catch (e) {} - } } - }, [embeddable, storage, timefilter, currentAppId]); + }, [embeddable, storage, timefilter, currentAppId, draftComment?.title]); return ( @@ -336,7 +310,7 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ - {editMode ? ( + {!!nodePosition ? ( handleEditInLensClick()} + onClick={handleEditInLensClick} > handleEditInLensClick()} + onClick={handleEditInLensClick} color="primary" size="m" iconType="lensApp" @@ -428,7 +402,7 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ {'Cancel'} - {editMode ? ( + {!!nodePosition ? ( - {editMode ? ( + {!!nodePosition ? ( { @@ -39,7 +42,7 @@ export const useLensDraftComment = () => { if (incomingEmbeddablePackage) { if (storage.get(DRAFT_COMMENT_STORAGE_ID)) { try { - setDraftComment(JSON.parse(storage.get(DRAFT_COMMENT_STORAGE_ID))); + setDraftComment(storage.get(DRAFT_COMMENT_STORAGE_ID)); // eslint-disable-next-line no-empty } catch (e) {} } @@ -48,18 +51,18 @@ export const useLensDraftComment = () => { fetchDraftComment(); }, [currentAppId$, embeddable, storage]); - const openLensModal = useCallback( - ({ editorRef }) => { - if (editorRef && editorRef.textarea && editorRef.toolbar) { - const lensPluginButton = editorRef.toolbar?.querySelector(`[aria-label="${INSERT_LENS}"]`); - if (lensPluginButton) { - lensPluginButton.click(); - storage.remove(DRAFT_COMMENT_STORAGE_ID); - } + const openLensModal = useCallback(({ editorRef }) => { + if (editorRef && editorRef.textarea && editorRef.toolbar) { + const lensPluginButton = editorRef.toolbar?.querySelector(`[aria-label="${INSERT_LENS}"]`); + if (lensPluginButton) { + lensPluginButton.click(); } - }, - [storage] - ); + } + }, []); + + const clearDraftComment = useCallback(() => { + storage.remove(DRAFT_COMMENT_STORAGE_ID); + }, [storage]); - return { draftComment, openLensModal }; + return { draftComment, openLensModal, clearDraftComment }; }; diff --git a/x-pack/plugins/cases/server/client/attachments/add.ts b/x-pack/plugins/cases/server/client/attachments/add.ts index dd1f09da5cb4a..26a5c2339dec2 100644 --- a/x-pack/plugins/cases/server/client/attachments/add.ts +++ b/x-pack/plugins/cases/server/client/attachments/add.ts @@ -10,6 +10,7 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; +import { EmbeddableStart } from 'src/plugins/embeddable/server'; import { SavedObject, SavedObjectsClientContract, @@ -124,6 +125,7 @@ const addGeneratedAlerts = async ( caseService, userActionService, logger, + embeddable, authorization, } = clientArgs; @@ -182,6 +184,7 @@ const addGeneratedAlerts = async ( unsecuredSavedObjectsClient, caseService, attachmentService, + embeddable, }); const { @@ -241,12 +244,14 @@ async function getCombinedCase({ unsecuredSavedObjectsClient, id, logger, + embeddable, }: { caseService: CasesService; attachmentService: AttachmentService; unsecuredSavedObjectsClient: SavedObjectsClientContract; id: string; logger: Logger; + embeddable: EmbeddableStart; }): Promise { const [casePromise, subCasePromise] = await Promise.allSettled([ caseService.getCase({ @@ -276,6 +281,7 @@ async function getCombinedCase({ caseService, attachmentService, unsecuredSavedObjectsClient, + embeddable, }); } else { throw Boom.badRequest('Sub case found without reference to collection'); @@ -291,6 +297,7 @@ async function getCombinedCase({ caseService, attachmentService, unsecuredSavedObjectsClient, + embeddable, }); } } @@ -332,6 +339,7 @@ export const addComment = async ( attachmentService, user, logger, + embeddable, authorization, } = clientArgs; @@ -362,6 +370,7 @@ export const addComment = async ( unsecuredSavedObjectsClient, id: caseId, logger, + embeddable, }); // eslint-disable-next-line @typescript-eslint/naming-convention diff --git a/x-pack/plugins/cases/server/client/attachments/update.ts b/x-pack/plugins/cases/server/client/attachments/update.ts index 157dd0b410898..fd0b2f3386dab 100644 --- a/x-pack/plugins/cases/server/client/attachments/update.ts +++ b/x-pack/plugins/cases/server/client/attachments/update.ts @@ -9,6 +9,7 @@ import { pick } from 'lodash/fp'; import Boom from '@hapi/boom'; import { SavedObjectsClientContract, Logger } from 'kibana/server'; +import { EmbeddableStart } from 'src/plugins/embeddable/server'; import { checkEnabledCaseConnectorOrThrow, CommentableCase, createCaseError } from '../../common'; import { buildCommentUserActionItem } from '../../services/user_actions/helpers'; import { @@ -46,6 +47,7 @@ interface CombinedCaseParams { unsecuredSavedObjectsClient: SavedObjectsClientContract; caseID: string; logger: Logger; + embeddable: EmbeddableStart; subCaseId?: string; } @@ -56,6 +58,7 @@ async function getCommentableCase({ caseID, subCaseId, logger, + embeddable, }: CombinedCaseParams) { if (subCaseId) { const [caseInfo, subCase] = await Promise.all([ @@ -75,6 +78,7 @@ async function getCommentableCase({ subCase, unsecuredSavedObjectsClient, logger, + embeddable, }); } else { const caseInfo = await caseService.getCase({ @@ -87,6 +91,7 @@ async function getCommentableCase({ collection: caseInfo, unsecuredSavedObjectsClient, logger, + embeddable, }); } } @@ -105,6 +110,7 @@ export async function update( caseService, unsecuredSavedObjectsClient, logger, + embeddable, user, userActionService, authorization, @@ -128,6 +134,7 @@ export async function update( caseID, subCaseId: subCaseID, logger, + embeddable, }); const myComment = await attachmentService.get({ diff --git a/x-pack/plugins/cases/server/client/factory.ts b/x-pack/plugins/cases/server/client/factory.ts index 8fcfbe934c3ad..619dab6b28d9f 100644 --- a/x-pack/plugins/cases/server/client/factory.ts +++ b/x-pack/plugins/cases/server/client/factory.ts @@ -11,6 +11,7 @@ import { Logger, ElasticsearchClient, } from 'kibana/server'; +import { EmbeddableStart } from 'src/plugins/embeddable/server'; import { SecurityPluginSetup, SecurityPluginStart } from '../../../security/server'; import { SAVED_OBJECT_TYPES } from '../../common'; import { Authorization } from '../authorization/authorization'; @@ -34,6 +35,7 @@ interface CasesClientFactoryArgs { getSpace: GetSpaceFn; featuresPluginStart: FeaturesPluginStart; actionsPluginStart: ActionsPluginStart; + embeddablePluginStart: EmbeddableStart; } /** @@ -108,6 +110,7 @@ export class CasesClientFactory { userActionService: new CaseUserActionService(this.logger), attachmentService: new AttachmentService(this.logger), logger: this.logger, + embeddable: this.options.embeddablePluginStart, authorization: auth, actionsClient: await this.options.actionsPluginStart.getActionsClientWithRequest(request), }); diff --git a/x-pack/plugins/cases/server/client/types.ts b/x-pack/plugins/cases/server/client/types.ts index ebf79519da59a..1ec9a3f88ef57 100644 --- a/x-pack/plugins/cases/server/client/types.ts +++ b/x-pack/plugins/cases/server/client/types.ts @@ -7,6 +7,7 @@ import type { PublicMethodsOf } from '@kbn/utility-types'; import { ElasticsearchClient, SavedObjectsClientContract, Logger } from 'kibana/server'; +import { EmbeddableStart } from 'src/plugins/embeddable/server'; import { User } from '../../common'; import { Authorization } from '../authorization/authorization'; import { @@ -33,6 +34,7 @@ export interface CasesClientArgs { readonly alertsService: AlertServiceContract; readonly attachmentService: AttachmentService; readonly logger: Logger; + readonly embeddable: EmbeddableStart; readonly authorization: PublicMethodsOf; readonly actionsClient: PublicMethodsOf; } diff --git a/x-pack/plugins/cases/server/common/models/commentable_case.ts b/x-pack/plugins/cases/server/common/models/commentable_case.ts index 0ef6d4ea99a0e..231488c7d0e7c 100644 --- a/x-pack/plugins/cases/server/common/models/commentable_case.ts +++ b/x-pack/plugins/cases/server/common/models/commentable_case.ts @@ -14,6 +14,7 @@ import { SavedObjectsUpdateResponse, Logger, } from 'src/core/server'; +import { EmbeddableStart } from 'src/plugins/embeddable/server'; import { AssociationType, CASE_SAVED_OBJECT, @@ -56,6 +57,7 @@ interface CommentableCaseParams { caseService: CasesService; attachmentService: AttachmentService; logger: Logger; + embeddable: EmbeddableStart; } /** @@ -69,6 +71,7 @@ export class CommentableCase { private readonly caseService: CasesService; private readonly attachmentService: AttachmentService; private readonly logger: Logger; + private readonly embeddable: EmbeddableStart; constructor({ collection, @@ -77,6 +80,7 @@ export class CommentableCase { caseService, attachmentService, logger, + embeddable, }: CommentableCaseParams) { this.collection = collection; this.subCase = subCase; @@ -84,6 +88,7 @@ export class CommentableCase { this.caseService = caseService; this.attachmentService = attachmentService; this.logger = logger; + this.embeddable = embeddable; } public get status(): CaseStatuses { @@ -191,6 +196,7 @@ export class CommentableCase { caseService: this.caseService, attachmentService: this.attachmentService, logger: this.logger, + embeddable: this.embeddable, }); } catch (error) { throw createCaseError({ @@ -226,6 +232,7 @@ export class CommentableCase { })) as SavedObject; const updatedReferences = getOrUpdateLensReferences( + this.embeddable, queryRestAttributes.comment, currentComment ); @@ -287,11 +294,14 @@ export class CommentableCase { throw Boom.badRequest('The owner field of the comment must match the case'); } - const references = this.buildRefsToCase(); + let references = this.buildRefsToCase(); if (commentReq.type === CommentType.user && commentReq?.comment) { - const commentStringReferences = getOrUpdateLensReferences(commentReq.comment); - references.concat(commentStringReferences); + const commentStringReferences = getOrUpdateLensReferences( + this.embeddable, + commentReq.comment + ); + references = [...references, ...commentStringReferences]; } const [comment, commentableCase] = await Promise.all([ diff --git a/x-pack/plugins/cases/server/common/utils.ts b/x-pack/plugins/cases/server/common/utils.ts index ec4cc07810002..1f2a77d5ef219 100644 --- a/x-pack/plugins/cases/server/common/utils.ts +++ b/x-pack/plugins/cases/server/common/utils.ts @@ -21,6 +21,8 @@ import { } from 'kibana/server'; import { filter, flatMap, uniqWith, isEmpty, xorWith } from 'lodash'; import { TimeRange } from 'src/plugins/data/server'; +import { EmbeddableStart } from 'src/plugins/embeddable/server'; +import { EmbeddableStateWithType } from 'src/plugins/embeddable/common'; import { AlertInfo } from '.'; import { LensDocShape714 } from '../../../lens/server'; @@ -417,7 +419,6 @@ export const getNoneCaseConnector = () => ({ interface LensMarkdownNode { timeRange: TimeRange; - editMode: boolean; attributes: LensDocShape714 & { references: SavedObjectReference[] }; } @@ -447,31 +448,39 @@ export const stringifyComment = (comment: Parent) => .stringify(comment); export const getLensVisualizations = (parsedComment: Array) => - filter(parsedComment, { type: LENS_ID }) as LensMarkdownNode[]; + filter(parsedComment, { type: LENS_ID }) as EmbeddableStateWithType[]; -export const extractLensReferencesFromCommentString = (comment: string): SavedObjectReference[] => { +export const extractLensReferencesFromCommentString = ( + embeddable: EmbeddableStart, + comment: string +): SavedObjectReference[] => { const parsedComment = parseCommentString(comment); const lensVisualizations = getLensVisualizations(parsedComment.children); - const flattenRefs = flatMap(lensVisualizations, (lensObject) => lensObject.attributes.references); + const flattenRefs = flatMap( + lensVisualizations, + (lensObject) => embeddable.extract(lensObject)?.references ?? [] + ); const uniqRefs = uniqWith( flattenRefs, - (refA, refB) => refA.type === refB.type && refA.id === refB.id + (refA, refB) => refA.type === refB.type && refA.id === refB.id && refA.name === refB.name ); return uniqRefs; }; export const getOrUpdateLensReferences = ( + embeddable: EmbeddableStart, newComment: string, currentComment?: SavedObject ) => { if (!currentComment) { - return extractLensReferencesFromCommentString(newComment); + return extractLensReferencesFromCommentString(embeddable, newComment); } const savedObjectReferences = currentComment.references; const savedObjectLensReferences = extractLensReferencesFromCommentString( + embeddable, currentComment.attributes.comment ); @@ -481,7 +490,7 @@ export const getOrUpdateLensReferences = ( (refA, refB) => refA.type === refB.type && refA.id === refB.id ); - const newCommentLensReferences = extractLensReferencesFromCommentString(newComment); + const newCommentLensReferences = extractLensReferencesFromCommentString(embeddable, newComment); return currentNonLensReferences.concat(newCommentLensReferences); }; diff --git a/x-pack/plugins/cases/server/plugin.ts b/x-pack/plugins/cases/server/plugin.ts index 59a06d8e99208..466393fa5b2b2 100644 --- a/x-pack/plugins/cases/server/plugin.ts +++ b/x-pack/plugins/cases/server/plugin.ts @@ -8,7 +8,6 @@ import { IContextProvider, KibanaRequest, Logger, PluginInitializerContext } from 'kibana/server'; import { CoreSetup, CoreStart } from 'src/core/server'; -import { LensPluginSetup } from '../../lens/server'; import { SecurityPluginSetup, SecurityPluginStart } from '../../security/server'; import { PluginSetupContract as ActionsPluginSetup, @@ -32,6 +31,7 @@ import { registerConnectors } from './connectors'; import type { CasesRequestHandlerContext } from './types'; import { CasesClientFactory } from './client/factory'; import { SpacesPluginStart } from '../../spaces/server'; +import { EmbeddableSetup, EmbeddableStart } from '../../../../src/plugins/embeddable/server'; import { PluginStartContract as FeaturesPluginStart } from '../../features/server'; function createConfig(context: PluginInitializerContext) { @@ -41,7 +41,7 @@ function createConfig(context: PluginInitializerContext) { export interface PluginsSetup { security?: SecurityPluginSetup; actions: ActionsPluginSetup; - lens: LensPluginSetup; + embeddable: EmbeddableSetup; } export interface PluginsStart { @@ -49,6 +49,7 @@ export interface PluginsStart { features: FeaturesPluginStart; spaces?: SpacesPluginStart; actions: ActionsPluginStart; + embeddable: EmbeddableStart; } /** @@ -84,7 +85,11 @@ export class CasePlugin { this.securityPluginSetup = plugins.security; core.savedObjects.registerType( - createCaseCommentSavedObjectType({ getLensMigrations: plugins.lens.getAllMigrations }) + createCaseCommentSavedObjectType({ + migrationDeps: { + embeddable: plugins.embeddable, + }, + }) ); core.savedObjects.registerType(caseConfigureSavedObjectType); core.savedObjects.registerType(caseConnectorMappingsSavedObjectType); @@ -131,6 +136,7 @@ export class CasePlugin { }, featuresPluginStart: plugins.features, actionsPluginStart: plugins.actions, + embeddablePluginStart: plugins.embeddable, }); const client = core.elasticsearch.client; diff --git a/x-pack/plugins/cases/server/saved_object_types/comments.ts b/x-pack/plugins/cases/server/saved_object_types/comments.ts index 67d0b1ccfe635..0384a65dcb389 100644 --- a/x-pack/plugins/cases/server/saved_object_types/comments.ts +++ b/x-pack/plugins/cases/server/saved_object_types/comments.ts @@ -9,11 +9,13 @@ import { SavedObjectsType } from 'src/core/server'; import { CASE_COMMENT_SAVED_OBJECT } from '../../common'; import { createCommentsMigrations, CreateCommentsMigrationsDeps } from './migrations'; -export const createCaseCommentSavedObjectType = ( - migrationDeps: CreateCommentsMigrationsDeps -): SavedObjectsType => ({ +export const createCaseCommentSavedObjectType = ({ + migrationDeps, +}: { + migrationDeps: CreateCommentsMigrationsDeps; +}): SavedObjectsType => ({ name: CASE_COMMENT_SAVED_OBJECT, - hidden: true, + hidden: false, namespaceType: 'single', mappings: { properties: { diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/index.test.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/index.test.ts index cc3a4cc01446a..0e910a525174c 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations/index.test.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/index.test.ts @@ -209,7 +209,9 @@ describe('Comments migrations', () => { it('should remove time zone param from date histogram', () => { const commentsMigrations714 = createCommentsMigrations({ - getLensMigrations: () => lensMigrations, + embeddable: { + getAllMigrations: () => lensMigrations, + }, }); const result = commentsMigrations714['7.14.0'](caseComment) as ReturnType< SavedObjectMigrationFn< diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/index.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/index.ts index 258dd31367765..cbd04722391af 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations/index.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/index.ts @@ -8,7 +8,8 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { flow, mapValues } from 'lodash'; -import { LensPluginSetup } from '../../../../lens/server'; +import { EmbeddableSetup } from '../../../../../../src/plugins/embeddable/server'; + import { mergeMigrationFunctionMaps, MigrateFunction, @@ -18,6 +19,7 @@ import { SavedObjectUnsanitizedDoc, SavedObjectSanitizedDoc, SavedObjectMigrationFn, + SavedObjectMigrationMap, } from '../../../../../../src/core/server'; import { ConnectorTypes, @@ -134,14 +136,20 @@ const migrateByValueLensVisualizations = ( }; export interface CreateCommentsMigrationsDeps { - getLensMigrations: LensPluginSetup['getAllMigrations']; + embeddable: EmbeddableSetup; } -export const createCommentsMigrations = (migrationDeps: CreateCommentsMigrationsDeps) => { - const lensMigrations = (mapValues( - migrationDeps.getLensMigrations(), +export const createCommentsMigrations = ( + migrationDeps: CreateCommentsMigrationsDeps +): SavedObjectMigrationMap => { + // console.error( + // 'migrationas', + // JSON.stringify(migrationDeps.embeddable.getAllMigrations(), null, 2) + // ); + const embeddableMigrations = mapValues( + migrationDeps.embeddable.getAllMigrations(), migrateByValueLensVisualizations - ) as unknown) as MigrateFunctionsObject; + ) as MigrateFunctionsObject; const commentsMigrations = { '7.11.0': flow( @@ -189,7 +197,7 @@ export const createCommentsMigrations = (migrationDeps: CreateCommentsMigrations ), }; - return mergeMigrationFunctionMaps(commentsMigrations, lensMigrations); + return mergeMigrationFunctionMaps(commentsMigrations, embeddableMigrations); }; export const connectorMappingsMigrations = { diff --git a/x-pack/plugins/lens/server/embeddable/lens_embeddable_factory.ts b/x-pack/plugins/lens/server/embeddable/lens_embeddable_factory.ts index 14a9713d8461e..1324e60303366 100644 --- a/x-pack/plugins/lens/server/embeddable/lens_embeddable_factory.ts +++ b/x-pack/plugins/lens/server/embeddable/lens_embeddable_factory.ts @@ -7,6 +7,8 @@ import { EmbeddableRegistryDefinition } from 'src/plugins/embeddable/server'; import type { SerializableRecord } from '@kbn/utility-types'; +import { SavedObjectReference } from 'kibana/server'; +import { EmbeddableStateWithType } from 'src/plugins/embeddable/common'; import { DOC_TYPE } from '../../common'; import { commonRemoveTimezoneDateHistogramParam, @@ -50,5 +52,16 @@ export const lensEmbeddableFactory = (): EmbeddableRegistryDefinition => { } as unknown) as SerializableRecord; }, }, + extract(state: EmbeddableStateWithType) { + let references: SavedObjectReference[] = []; + const typedState = (state as unknown) as SerializableRecord; + + if ('attributes' in typedState && typedState.attributes !== undefined) { + // @ts-expect-error + references = typedState?.attributes?.references || []; + } + + return { state, references }; + }, }; }; diff --git a/x-pack/plugins/lens/server/plugin.tsx b/x-pack/plugins/lens/server/plugin.tsx index e15842f78a5a1..f0ee801ece89b 100644 --- a/x-pack/plugins/lens/server/plugin.tsx +++ b/x-pack/plugins/lens/server/plugin.tsx @@ -21,7 +21,6 @@ import { import { setupSavedObjects } from './saved_objects'; import { EmbeddableSetup } from '../../../../src/plugins/embeddable/server'; import { lensEmbeddableFactory } from './embeddable/lens_embeddable_factory'; -import { migrations } from './migrations/saved_object_migrations'; import { setupExpressions } from './expressions'; export interface PluginSetupContract { @@ -37,10 +36,6 @@ export interface PluginStartContract { data: DataPluginStart; } -export interface LensPluginSetup { - getAllMigrations: () => typeof migrations; -} - export class LensServerPlugin implements Plugin<{}, {}, {}, {}> { private readonly kibanaIndexConfig: Observable<{ kibana: { index: string } }>; private readonly telemetryLogger: Logger; @@ -49,7 +44,7 @@ export class LensServerPlugin implements Plugin<{}, {}, {}, {}> { this.kibanaIndexConfig = initializerContext.config.legacy.globalConfig$; this.telemetryLogger = initializerContext.logger.get('usage'); } - setup(core: CoreSetup, plugins: PluginSetupContract): LensPluginSetup { + setup(core: CoreSetup, plugins: PluginSetupContract) { setupSavedObjects(core); setupRoutes(core, this.initializerContext.logger.get()); setupExpressions(core, plugins.expressions); @@ -69,9 +64,7 @@ export class LensServerPlugin implements Plugin<{}, {}, {}, {}> { ); } plugins.embeddable.registerEmbeddableFactory(lensEmbeddableFactory()); - return { - getAllMigrations: () => migrations, - }; + return {}; } start(core: CoreStart, plugins: PluginStartContract) { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/action_menu/action_menu.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/action_menu/action_menu.test.tsx deleted file mode 100644 index 09d7ab21cffdf..0000000000000 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/action_menu/action_menu.test.tsx +++ /dev/null @@ -1,59 +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. - */ - -import { render } from '../../rtl_helpers'; -import { fireEvent, screen } from '@testing-library/dom'; -import React from 'react'; -import { sampleAttribute } from '../../configurations/test_data/sample_attribute'; -import * as pluginHook from '../../../../../hooks/use_plugin_context'; -import { TypedLensByValueInput } from '../../../../../../../lens/public'; -import { ExpViewActionMenuContent } from './action_menu'; - -jest.spyOn(pluginHook, 'usePluginContext').mockReturnValue({ - appMountParameters: { - setHeaderActionMenu: jest.fn(), - }, -} as any); - -describe('Action Menu', function () { - it('should be able to click open in lens', async function () { - const { findByText, core } = render( - - ); - - expect(await screen.findByText('Open in Lens')).toBeInTheDocument(); - - fireEvent.click(await findByText('Open in Lens')); - - expect(core.lens?.navigateToPrefilledEditor).toHaveBeenCalledTimes(1); - expect(core.lens?.navigateToPrefilledEditor).toHaveBeenCalledWith( - { - id: '', - attributes: sampleAttribute, - timeRange: { to: 'now', from: 'now-10m' }, - }, - { openInNewTab: true } - ); - }); - - it('should be able to click save', async function () { - const { findByText } = render( - - ); - - expect(await screen.findByText('Save')).toBeInTheDocument(); - - fireEvent.click(await findByText('Save')); - - expect(await screen.findByText('Lens Save Modal Component')).toBeInTheDocument(); - }); -}); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/action_menu/action_menu.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/action_menu/action_menu.tsx deleted file mode 100644 index 06c98686f37e9..0000000000000 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/action_menu/action_menu.tsx +++ /dev/null @@ -1,94 +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. - */ - -import React, { useState } from 'react'; -import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { LensEmbeddableInput, TypedLensByValueInput } from '../../../../../../../lens/public'; -import { ObservabilityAppServices } from '../../../../../application/types'; -import { useKibana } from '../../../../../../../../../src/plugins/kibana_react/public'; - -export function ExpViewActionMenuContent({ - timeRange, - lensAttributes, -}: { - timeRange?: { from: string; to: string }; - lensAttributes: TypedLensByValueInput['attributes'] | null; -}) { - const kServices = useKibana().services; - - const { lens } = kServices; - - const [isSaveOpen, setIsSaveOpen] = useState(false); - - const LensSaveModalComponent = lens.SaveModalComponent; - - return ( - <> - - - { - if (lensAttributes) { - lens.navigateToPrefilledEditor( - { - id: '', - timeRange, - attributes: lensAttributes, - }, - { - openInNewTab: true, - } - ); - } - }} - > - {i18n.translate('xpack.observability.expView.heading.openInLens', { - defaultMessage: 'Open in Lens', - })} - - - - { - if (lensAttributes) { - setIsSaveOpen(true); - } - }} - size="s" - > - {i18n.translate('xpack.observability.expView.heading.saveLensVisualization', { - defaultMessage: 'Save', - })} - - - - - {isSaveOpen && lensAttributes && ( - setIsSaveOpen(false)} - // if we want to do anything after the viz is saved - // right now there is no action, so an empty function - onSave={() => {}} - /> - )} - - ); -} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.test.tsx index 8cd8977fcf741..62d828b337c2d 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.test.tsx @@ -52,7 +52,7 @@ describe('ExploratoryViewHeader', function () { to: 'now', }, }, - true + { openInNewTab: true } ); }); }); From 7775cb88ed8f54574092a35da8fff26b42c5fa5d Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Mon, 16 Aug 2021 00:21:25 +0200 Subject: [PATCH 39/41] cleanup --- .../cases/server/saved_object_types/migrations/index.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/index.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/index.ts index cbd04722391af..b7da708ae7f07 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations/index.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/index.ts @@ -142,10 +142,6 @@ export interface CreateCommentsMigrationsDeps { export const createCommentsMigrations = ( migrationDeps: CreateCommentsMigrationsDeps ): SavedObjectMigrationMap => { - // console.error( - // 'migrationas', - // JSON.stringify(migrationDeps.embeddable.getAllMigrations(), null, 2) - // ); const embeddableMigrations = mapValues( migrationDeps.embeddable.getAllMigrations(), migrateByValueLensVisualizations From 5513a45a3f2142d21e084a33be14766f7a35eb5e Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Mon, 16 Aug 2021 08:48:02 +0200 Subject: [PATCH 40/41] update unit tests --- .../plugins/cases/server/common/utils.test.ts | 24 +++++++++++++------ .../migrations/index.test.ts | 4 ++-- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/cases/server/common/utils.test.ts b/x-pack/plugins/cases/server/common/utils.test.ts index 16455c325d16b..a6dfd53be53aa 100644 --- a/x-pack/plugins/cases/server/common/utils.test.ts +++ b/x-pack/plugins/cases/server/common/utils.test.ts @@ -6,6 +6,7 @@ */ import { SavedObject, SavedObjectsFindResponse } from 'kibana/server'; +import { lensEmbeddableFactory } from '../../../lens/server/embeddable/lens_embeddable_factory'; import { SECURITY_SOLUTION_OWNER } from '../../common'; import { AssociationType, @@ -871,6 +872,7 @@ describe('common utils', () => { describe('extractLensReferencesFromCommentString', () => { it('extracts successfully', () => { + const lensEmbeddable = lensEmbeddableFactory(); const commentString = [ '**Test** ', 'Amazingg!!!', @@ -879,7 +881,10 @@ describe('common utils', () => { '!{lens{"timeRange":{"from":"now-7d","to":"now","mode":"relative"},"attributes":{"title":"aaaa","type":"lens","visualizationType":"lnsXY","state":{"datasourceStates":{"indexpattern":{"layers":{"layer1":{"columnOrder":["col1","col2"],"columns":{"col2":{"dataType":"number","isBucketed":false,"label":"Count of records","operationType":"count","scale":"ratio","sourceField":"Records"},"col1":{"dataType":"date","isBucketed":true,"label":"@timestamp","operationType":"date_histogram","params":{"interval":"auto"},"scale":"interval","sourceField":"timestamp"}}}}}},"visualization":{"axisTitlesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"fittingFunction":"None","gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"layers":[{"accessors":["col2"],"layerId":"layer1","seriesType":"bar_stacked","xAccessor":"col1","yConfig":[{"forAccessor":"col2"}]}],"legend":{"isVisible":true,"position":"right"},"preferredSeriesType":"bar_stacked","tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"valueLabels":"hide","yRightExtent":{"mode":"full"}},"query":{"language":"kuery","query":""},"filters":[]},"references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b246","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-layer1"}]},"editMode":false}}', ].join('\n\n'); - const extractedReferences = extractLensReferencesFromCommentString(commentString); + const extractedReferences = extractLensReferencesFromCommentString( + lensEmbeddable, + commentString + ); const expectedReferences = [ { @@ -906,6 +911,7 @@ describe('common utils', () => { describe('getOrUpdateLensReferences', () => { it('update references', () => { + const lensEmbeddable = lensEmbeddableFactory(); const currentCommentStringReferences = [ [ { @@ -975,12 +981,16 @@ describe('common utils', () => { )}},"editMode":false}}`, ].join('\n\n'); - const updatedReferences = getOrUpdateLensReferences(newCommentString, { - references: currentCommentReferences, - attributes: { - comment: currentCommentString, - }, - } as SavedObject); + const updatedReferences = getOrUpdateLensReferences( + lensEmbeddable as EmbeddableStart, + newCommentString, + { + references: currentCommentReferences, + attributes: { + comment: currentCommentString, + }, + } as SavedObject + ); const expectedReferences = [ ...nonLensCurrentCommentReferences, diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/index.test.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/index.test.ts index 0e910a525174c..8940704986efc 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations/index.test.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/index.test.ts @@ -151,9 +151,9 @@ describe('Comments migrations', () => { }, }; - const expectedMigrationCommentResult = `"**Amazing**\n\n!{tooltip[Tessss](https://example.com)}\n\nbrbrbr\n\n[asdasdasdasd](http://localhost:5601/moq/app/security/timelines?timeline=(id%3A%27e4362a60-f478-11eb-a4b0-ebefce184d8d%27%2CisOpen%3A!t))\n\n!{lens{\"timeRange\":{\"from\":\"now-7d\",\"to\":\"now\",\"mode\":\"relative\"},\"editMode\":false,\"attributes\":${JSON.stringify( + const expectedMigrationCommentResult = `"**Amazing**\n\n!{tooltip[Tessss](https://example.com)}\n\nbrbrbr\n\n[asdasdasdasd](http://localhost:5601/moq/app/security/timelines?timeline=(id%3A%27e4362a60-f478-11eb-a4b0-ebefce184d8d%27%2CisOpen%3A!t))\n\n!{lens{\"timeRange\":{\"from\":\"now-7d\",\"to\":\"now\",\"mode\":\"relative\"},\"attributes\":${JSON.stringify( expectedLensVisualizationMigrated - )}}}\n\n!{lens{\"timeRange\":{\"from\":\"now-7d\",\"to\":\"now\",\"mode\":\"relative\"},\"editMode\":false,\"attributes\":{\"title\":\"TEst22\",\"type\":\"lens\",\"visualizationType\":\"lnsMetric\",\"state\":{\"datasourceStates\":{\"indexpattern\":{\"layers\":{\"layer1\":{\"columnOrder\":[\"col2\"],\"columns\":{\"col2\":{\"dataType\":\"number\",\"isBucketed\":false,\"label\":\"Count of records\",\"operationType\":\"count\",\"scale\":\"ratio\",\"sourceField\":\"Records\"}}}}}},\"visualization\":{\"layerId\":\"layer1\",\"accessor\":\"col2\"},\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filters\":[]},\"references\":[{\"type\":\"index-pattern\",\"id\":\"90943e30-9a47-11e8-b64d-95841ca0b247\",\"name\":\"indexpattern-datasource-current-indexpattern\"},{\"type\":\"index-pattern\",\"id\":\"90943e30-9a47-11e8-b64d-95841ca0b247\",\"name\":\"indexpattern-datasource-layer-layer1\"}]}}}\n\nbrbrbr" + )}}}\n\n!{lens{\"timeRange\":{\"from\":\"now-7d\",\"to\":\"now\",\"mode\":\"relative\"},\"attributes\":{\"title\":\"TEst22\",\"type\":\"lens\",\"visualizationType\":\"lnsMetric\",\"state\":{\"datasourceStates\":{\"indexpattern\":{\"layers\":{\"layer1\":{\"columnOrder\":[\"col2\"],\"columns\":{\"col2\":{\"dataType\":\"number\",\"isBucketed\":false,\"label\":\"Count of records\",\"operationType\":\"count\",\"scale\":\"ratio\",\"sourceField\":\"Records\"}}}}}},\"visualization\":{\"layerId\":\"layer1\",\"accessor\":\"col2\"},\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filters\":[]},\"references\":[{\"type\":\"index-pattern\",\"id\":\"90943e30-9a47-11e8-b64d-95841ca0b247\",\"name\":\"indexpattern-datasource-current-indexpattern\"},{\"type\":\"index-pattern\",\"id\":\"90943e30-9a47-11e8-b64d-95841ca0b247\",\"name\":\"indexpattern-datasource-layer-layer1\"}]}}}\n\nbrbrbr" `; const caseComment = { From 317e7dc7b6d14ec0c5bb959ea23365e823ab73c3 Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Mon, 16 Aug 2021 15:48:19 +0200 Subject: [PATCH 41/41] PR comments --- x-pack/plugins/cases/kibana.json | 1 - .../cases/server/client/attachments/add.ts | 18 +++---- .../cases/server/client/attachments/update.ts | 14 +++--- x-pack/plugins/cases/server/client/factory.ts | 7 +-- x-pack/plugins/cases/server/client/types.ts | 4 +- .../server/common/models/commentable_case.ts | 16 +++--- .../plugins/cases/server/common/utils.test.ts | 20 +++----- x-pack/plugins/cases/server/common/utils.ts | 49 +++++++++++-------- x-pack/plugins/cases/server/plugin.ts | 11 +++-- .../migrations/index.test.ts | 32 +++++------- .../saved_object_types/migrations/index.ts | 6 +-- .../lens/common/embeddable_factory/index.ts | 36 ++++++++++++++ .../public/embeddable/embeddable_factory.ts | 16 ++---- .../embeddable/lens_embeddable_factory.ts | 16 ++---- x-pack/plugins/lens/server/index.ts | 1 + x-pack/plugins/lens/server/plugin.tsx | 11 ++++- x-pack/plugins/observability/kibana.json | 1 - x-pack/plugins/security_solution/kibana.json | 2 +- 18 files changed, 141 insertions(+), 120 deletions(-) create mode 100644 x-pack/plugins/lens/common/embeddable_factory/index.ts diff --git a/x-pack/plugins/cases/kibana.json b/x-pack/plugins/cases/kibana.json index 13410895b1151..ebac6295166df 100644 --- a/x-pack/plugins/cases/kibana.json +++ b/x-pack/plugins/cases/kibana.json @@ -19,7 +19,6 @@ }, "requiredPlugins":[ "actions", - "embeddable", "esUiShared", "lens", "features", diff --git a/x-pack/plugins/cases/server/client/attachments/add.ts b/x-pack/plugins/cases/server/client/attachments/add.ts index 26a5c2339dec2..166ae2ae65012 100644 --- a/x-pack/plugins/cases/server/client/attachments/add.ts +++ b/x-pack/plugins/cases/server/client/attachments/add.ts @@ -10,13 +10,13 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; -import { EmbeddableStart } from 'src/plugins/embeddable/server'; import { SavedObject, SavedObjectsClientContract, Logger, SavedObjectsUtils, } from '../../../../../../src/core/server'; +import { LensServerPluginSetup } from '../../../../lens/server'; import { nodeBuilder } from '../../../../../../src/plugins/data/common'; import { @@ -125,7 +125,7 @@ const addGeneratedAlerts = async ( caseService, userActionService, logger, - embeddable, + lensEmbeddableFactory, authorization, } = clientArgs; @@ -184,7 +184,7 @@ const addGeneratedAlerts = async ( unsecuredSavedObjectsClient, caseService, attachmentService, - embeddable, + lensEmbeddableFactory, }); const { @@ -244,14 +244,14 @@ async function getCombinedCase({ unsecuredSavedObjectsClient, id, logger, - embeddable, + lensEmbeddableFactory, }: { caseService: CasesService; attachmentService: AttachmentService; unsecuredSavedObjectsClient: SavedObjectsClientContract; id: string; logger: Logger; - embeddable: EmbeddableStart; + lensEmbeddableFactory: LensServerPluginSetup['lensEmbeddableFactory']; }): Promise { const [casePromise, subCasePromise] = await Promise.allSettled([ caseService.getCase({ @@ -281,7 +281,7 @@ async function getCombinedCase({ caseService, attachmentService, unsecuredSavedObjectsClient, - embeddable, + lensEmbeddableFactory, }); } else { throw Boom.badRequest('Sub case found without reference to collection'); @@ -297,7 +297,7 @@ async function getCombinedCase({ caseService, attachmentService, unsecuredSavedObjectsClient, - embeddable, + lensEmbeddableFactory, }); } } @@ -339,7 +339,7 @@ export const addComment = async ( attachmentService, user, logger, - embeddable, + lensEmbeddableFactory, authorization, } = clientArgs; @@ -370,7 +370,7 @@ export const addComment = async ( unsecuredSavedObjectsClient, id: caseId, logger, - embeddable, + lensEmbeddableFactory, }); // eslint-disable-next-line @typescript-eslint/naming-convention diff --git a/x-pack/plugins/cases/server/client/attachments/update.ts b/x-pack/plugins/cases/server/client/attachments/update.ts index fd0b2f3386dab..da505ed55313c 100644 --- a/x-pack/plugins/cases/server/client/attachments/update.ts +++ b/x-pack/plugins/cases/server/client/attachments/update.ts @@ -9,7 +9,7 @@ import { pick } from 'lodash/fp'; import Boom from '@hapi/boom'; import { SavedObjectsClientContract, Logger } from 'kibana/server'; -import { EmbeddableStart } from 'src/plugins/embeddable/server'; +import { LensServerPluginSetup } from '../../../../lens/server'; import { checkEnabledCaseConnectorOrThrow, CommentableCase, createCaseError } from '../../common'; import { buildCommentUserActionItem } from '../../services/user_actions/helpers'; import { @@ -47,7 +47,7 @@ interface CombinedCaseParams { unsecuredSavedObjectsClient: SavedObjectsClientContract; caseID: string; logger: Logger; - embeddable: EmbeddableStart; + lensEmbeddableFactory: LensServerPluginSetup['lensEmbeddableFactory']; subCaseId?: string; } @@ -58,7 +58,7 @@ async function getCommentableCase({ caseID, subCaseId, logger, - embeddable, + lensEmbeddableFactory, }: CombinedCaseParams) { if (subCaseId) { const [caseInfo, subCase] = await Promise.all([ @@ -78,7 +78,7 @@ async function getCommentableCase({ subCase, unsecuredSavedObjectsClient, logger, - embeddable, + lensEmbeddableFactory, }); } else { const caseInfo = await caseService.getCase({ @@ -91,7 +91,7 @@ async function getCommentableCase({ collection: caseInfo, unsecuredSavedObjectsClient, logger, - embeddable, + lensEmbeddableFactory, }); } } @@ -110,7 +110,7 @@ export async function update( caseService, unsecuredSavedObjectsClient, logger, - embeddable, + lensEmbeddableFactory, user, userActionService, authorization, @@ -134,7 +134,7 @@ export async function update( caseID, subCaseId: subCaseID, logger, - embeddable, + lensEmbeddableFactory, }); const myComment = await attachmentService.get({ diff --git a/x-pack/plugins/cases/server/client/factory.ts b/x-pack/plugins/cases/server/client/factory.ts index 619dab6b28d9f..2fae6996f4aa2 100644 --- a/x-pack/plugins/cases/server/client/factory.ts +++ b/x-pack/plugins/cases/server/client/factory.ts @@ -11,7 +11,6 @@ import { Logger, ElasticsearchClient, } from 'kibana/server'; -import { EmbeddableStart } from 'src/plugins/embeddable/server'; import { SecurityPluginSetup, SecurityPluginStart } from '../../../security/server'; import { SAVED_OBJECT_TYPES } from '../../common'; import { Authorization } from '../authorization/authorization'; @@ -26,6 +25,8 @@ import { } from '../services'; import { PluginStartContract as FeaturesPluginStart } from '../../../features/server'; import { PluginStartContract as ActionsPluginStart } from '../../../actions/server'; +import { LensServerPluginSetup } from '../../../lens/server'; + import { AuthorizationAuditLogger } from '../authorization'; import { CasesClient, createCasesClient } from '.'; @@ -35,7 +36,7 @@ interface CasesClientFactoryArgs { getSpace: GetSpaceFn; featuresPluginStart: FeaturesPluginStart; actionsPluginStart: ActionsPluginStart; - embeddablePluginStart: EmbeddableStart; + lensEmbeddableFactory: LensServerPluginSetup['lensEmbeddableFactory']; } /** @@ -110,7 +111,7 @@ export class CasesClientFactory { userActionService: new CaseUserActionService(this.logger), attachmentService: new AttachmentService(this.logger), logger: this.logger, - embeddable: this.options.embeddablePluginStart, + lensEmbeddableFactory: this.options.lensEmbeddableFactory, authorization: auth, actionsClient: await this.options.actionsPluginStart.getActionsClientWithRequest(request), }); diff --git a/x-pack/plugins/cases/server/client/types.ts b/x-pack/plugins/cases/server/client/types.ts index 1ec9a3f88ef57..27829d2539c7d 100644 --- a/x-pack/plugins/cases/server/client/types.ts +++ b/x-pack/plugins/cases/server/client/types.ts @@ -7,7 +7,6 @@ import type { PublicMethodsOf } from '@kbn/utility-types'; import { ElasticsearchClient, SavedObjectsClientContract, Logger } from 'kibana/server'; -import { EmbeddableStart } from 'src/plugins/embeddable/server'; import { User } from '../../common'; import { Authorization } from '../authorization/authorization'; import { @@ -19,6 +18,7 @@ import { AttachmentService, } from '../services'; import { ActionsClient } from '../../../actions/server'; +import { LensServerPluginSetup } from '../../../lens/server'; /** * Parameters for initializing a cases client @@ -34,7 +34,7 @@ export interface CasesClientArgs { readonly alertsService: AlertServiceContract; readonly attachmentService: AttachmentService; readonly logger: Logger; - readonly embeddable: EmbeddableStart; + readonly lensEmbeddableFactory: LensServerPluginSetup['lensEmbeddableFactory']; readonly authorization: PublicMethodsOf; readonly actionsClient: PublicMethodsOf; } diff --git a/x-pack/plugins/cases/server/common/models/commentable_case.ts b/x-pack/plugins/cases/server/common/models/commentable_case.ts index 231488c7d0e7c..856d6378d5900 100644 --- a/x-pack/plugins/cases/server/common/models/commentable_case.ts +++ b/x-pack/plugins/cases/server/common/models/commentable_case.ts @@ -14,7 +14,7 @@ import { SavedObjectsUpdateResponse, Logger, } from 'src/core/server'; -import { EmbeddableStart } from 'src/plugins/embeddable/server'; +import { LensServerPluginSetup } from '../../../../lens/server'; import { AssociationType, CASE_SAVED_OBJECT, @@ -57,7 +57,7 @@ interface CommentableCaseParams { caseService: CasesService; attachmentService: AttachmentService; logger: Logger; - embeddable: EmbeddableStart; + lensEmbeddableFactory: LensServerPluginSetup['lensEmbeddableFactory']; } /** @@ -71,7 +71,7 @@ export class CommentableCase { private readonly caseService: CasesService; private readonly attachmentService: AttachmentService; private readonly logger: Logger; - private readonly embeddable: EmbeddableStart; + private readonly lensEmbeddableFactory: LensServerPluginSetup['lensEmbeddableFactory']; constructor({ collection, @@ -80,7 +80,7 @@ export class CommentableCase { caseService, attachmentService, logger, - embeddable, + lensEmbeddableFactory, }: CommentableCaseParams) { this.collection = collection; this.subCase = subCase; @@ -88,7 +88,7 @@ export class CommentableCase { this.caseService = caseService; this.attachmentService = attachmentService; this.logger = logger; - this.embeddable = embeddable; + this.lensEmbeddableFactory = lensEmbeddableFactory; } public get status(): CaseStatuses { @@ -196,7 +196,7 @@ export class CommentableCase { caseService: this.caseService, attachmentService: this.attachmentService, logger: this.logger, - embeddable: this.embeddable, + lensEmbeddableFactory: this.lensEmbeddableFactory, }); } catch (error) { throw createCaseError({ @@ -232,7 +232,7 @@ export class CommentableCase { })) as SavedObject; const updatedReferences = getOrUpdateLensReferences( - this.embeddable, + this.lensEmbeddableFactory, queryRestAttributes.comment, currentComment ); @@ -298,7 +298,7 @@ export class CommentableCase { if (commentReq.type === CommentType.user && commentReq?.comment) { const commentStringReferences = getOrUpdateLensReferences( - this.embeddable, + this.lensEmbeddableFactory, commentReq.comment ); references = [...references, ...commentStringReferences]; diff --git a/x-pack/plugins/cases/server/common/utils.test.ts b/x-pack/plugins/cases/server/common/utils.test.ts index a6dfd53be53aa..e45b91a28ceb3 100644 --- a/x-pack/plugins/cases/server/common/utils.test.ts +++ b/x-pack/plugins/cases/server/common/utils.test.ts @@ -872,7 +872,6 @@ describe('common utils', () => { describe('extractLensReferencesFromCommentString', () => { it('extracts successfully', () => { - const lensEmbeddable = lensEmbeddableFactory(); const commentString = [ '**Test** ', 'Amazingg!!!', @@ -882,7 +881,7 @@ describe('common utils', () => { ].join('\n\n'); const extractedReferences = extractLensReferencesFromCommentString( - lensEmbeddable, + lensEmbeddableFactory, commentString ); @@ -911,7 +910,6 @@ describe('common utils', () => { describe('getOrUpdateLensReferences', () => { it('update references', () => { - const lensEmbeddable = lensEmbeddableFactory(); const currentCommentStringReferences = [ [ { @@ -981,16 +979,12 @@ describe('common utils', () => { )}},"editMode":false}}`, ].join('\n\n'); - const updatedReferences = getOrUpdateLensReferences( - lensEmbeddable as EmbeddableStart, - newCommentString, - { - references: currentCommentReferences, - attributes: { - comment: currentCommentString, - }, - } as SavedObject - ); + const updatedReferences = getOrUpdateLensReferences(lensEmbeddableFactory, newCommentString, { + references: currentCommentReferences, + attributes: { + comment: currentCommentString, + }, + } as SavedObject); const expectedReferences = [ ...nonLensCurrentCommentReferences, diff --git a/x-pack/plugins/cases/server/common/utils.ts b/x-pack/plugins/cases/server/common/utils.ts index 1f2a77d5ef219..ba7d56f51eea9 100644 --- a/x-pack/plugins/cases/server/common/utils.ts +++ b/x-pack/plugins/cases/server/common/utils.ts @@ -21,10 +21,9 @@ import { } from 'kibana/server'; import { filter, flatMap, uniqWith, isEmpty, xorWith } from 'lodash'; import { TimeRange } from 'src/plugins/data/server'; -import { EmbeddableStart } from 'src/plugins/embeddable/server'; import { EmbeddableStateWithType } from 'src/plugins/embeddable/common'; import { AlertInfo } from '.'; -import { LensDocShape714 } from '../../../lens/server'; +import { LensServerPluginSetup, LensDocShape715 } from '../../../lens/server'; import { AssociationType, @@ -417,9 +416,9 @@ export const getNoneCaseConnector = () => ({ fields: null, }); -interface LensMarkdownNode { +interface LensMarkdownNode extends EmbeddableStateWithType { timeRange: TimeRange; - attributes: LensDocShape714 & { references: SavedObjectReference[] }; + attributes: LensDocShape715 & { references: SavedObjectReference[] }; } export const parseCommentString = (comment: string) => { @@ -448,39 +447,44 @@ export const stringifyComment = (comment: Parent) => .stringify(comment); export const getLensVisualizations = (parsedComment: Array) => - filter(parsedComment, { type: LENS_ID }) as EmbeddableStateWithType[]; + filter(parsedComment, { type: LENS_ID }) as LensMarkdownNode[]; export const extractLensReferencesFromCommentString = ( - embeddable: EmbeddableStart, + lensEmbeddableFactory: LensServerPluginSetup['lensEmbeddableFactory'], comment: string ): SavedObjectReference[] => { - const parsedComment = parseCommentString(comment); - const lensVisualizations = getLensVisualizations(parsedComment.children); - const flattenRefs = flatMap( - lensVisualizations, - (lensObject) => embeddable.extract(lensObject)?.references ?? [] - ); + const extract = lensEmbeddableFactory()?.extract; + + if (extract) { + const parsedComment = parseCommentString(comment); + const lensVisualizations = getLensVisualizations(parsedComment.children); + const flattenRefs = flatMap( + lensVisualizations, + (lensObject) => extract(lensObject)?.references ?? [] + ); - const uniqRefs = uniqWith( - flattenRefs, - (refA, refB) => refA.type === refB.type && refA.id === refB.id && refA.name === refB.name - ); + const uniqRefs = uniqWith( + flattenRefs, + (refA, refB) => refA.type === refB.type && refA.id === refB.id && refA.name === refB.name + ); - return uniqRefs; + return uniqRefs; + } + return []; }; export const getOrUpdateLensReferences = ( - embeddable: EmbeddableStart, + lensEmbeddableFactory: LensServerPluginSetup['lensEmbeddableFactory'], newComment: string, currentComment?: SavedObject ) => { if (!currentComment) { - return extractLensReferencesFromCommentString(embeddable, newComment); + return extractLensReferencesFromCommentString(lensEmbeddableFactory, newComment); } const savedObjectReferences = currentComment.references; const savedObjectLensReferences = extractLensReferencesFromCommentString( - embeddable, + lensEmbeddableFactory, currentComment.attributes.comment ); @@ -490,7 +494,10 @@ export const getOrUpdateLensReferences = ( (refA, refB) => refA.type === refB.type && refA.id === refB.id ); - const newCommentLensReferences = extractLensReferencesFromCommentString(embeddable, newComment); + const newCommentLensReferences = extractLensReferencesFromCommentString( + lensEmbeddableFactory, + newComment + ); return currentNonLensReferences.concat(newCommentLensReferences); }; diff --git a/x-pack/plugins/cases/server/plugin.ts b/x-pack/plugins/cases/server/plugin.ts index 466393fa5b2b2..bb1be163585a8 100644 --- a/x-pack/plugins/cases/server/plugin.ts +++ b/x-pack/plugins/cases/server/plugin.ts @@ -31,8 +31,8 @@ import { registerConnectors } from './connectors'; import type { CasesRequestHandlerContext } from './types'; import { CasesClientFactory } from './client/factory'; import { SpacesPluginStart } from '../../spaces/server'; -import { EmbeddableSetup, EmbeddableStart } from '../../../../src/plugins/embeddable/server'; import { PluginStartContract as FeaturesPluginStart } from '../../features/server'; +import { LensServerPluginSetup } from '../../lens/server'; function createConfig(context: PluginInitializerContext) { return context.config.get(); @@ -41,7 +41,7 @@ function createConfig(context: PluginInitializerContext) { export interface PluginsSetup { security?: SecurityPluginSetup; actions: ActionsPluginSetup; - embeddable: EmbeddableSetup; + lens: LensServerPluginSetup; } export interface PluginsStart { @@ -49,7 +49,6 @@ export interface PluginsStart { features: FeaturesPluginStart; spaces?: SpacesPluginStart; actions: ActionsPluginStart; - embeddable: EmbeddableStart; } /** @@ -69,6 +68,7 @@ export class CasePlugin { private readonly log: Logger; private clientFactory: CasesClientFactory; private securityPluginSetup?: SecurityPluginSetup; + private lensEmbeddableFactory?: LensServerPluginSetup['lensEmbeddableFactory']; constructor(private readonly initializerContext: PluginInitializerContext) { this.log = this.initializerContext.logger.get(); @@ -83,11 +83,12 @@ export class CasePlugin { } this.securityPluginSetup = plugins.security; + this.lensEmbeddableFactory = plugins.lens.lensEmbeddableFactory; core.savedObjects.registerType( createCaseCommentSavedObjectType({ migrationDeps: { - embeddable: plugins.embeddable, + lensEmbeddableFactory: this.lensEmbeddableFactory, }, }) ); @@ -136,7 +137,7 @@ export class CasePlugin { }, featuresPluginStart: plugins.features, actionsPluginStart: plugins.actions, - embeddablePluginStart: plugins.embeddable, + lensEmbeddableFactory: this.lensEmbeddableFactory!, }); const client = core.elasticsearch.client; diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/index.test.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/index.test.ts index 8940704986efc..595ecf290c520 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations/index.test.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/index.test.ts @@ -5,15 +5,19 @@ * 2.0. */ -import { - LensDocShape, - migrations as lensMigrations, -} from '../../../../lens/server/migrations/saved_object_migrations'; import { createCommentsMigrations } from './index'; -import { SavedObjectMigrationFn } from 'src/core/server'; import { getLensVisualizations, parseCommentString } from '../../common'; -describe('Comments migrations', () => { +import { savedObjectsServiceMock } from '../../../../../../src/core/server/mocks'; +import { lensEmbeddableFactory } from '../../../../lens/server/embeddable/lens_embeddable_factory'; + +const migrations = createCommentsMigrations({ + lensEmbeddableFactory, +}); + +const contextMock = savedObjectsServiceMock.createMigrationContext(); + +describe('lens embeddable migrations for by value panels', () => { describe('7.14.0 remove time zone from Lens visualization date histogram', () => { const lensVisualizationToMigrate = { title: 'MyRenamedOps', @@ -208,19 +212,9 @@ describe('Comments migrations', () => { }; it('should remove time zone param from date histogram', () => { - const commentsMigrations714 = createCommentsMigrations({ - embeddable: { - getAllMigrations: () => lensMigrations, - }, - }); - const result = commentsMigrations714['7.14.0'](caseComment) as ReturnType< - SavedObjectMigrationFn< - LensDocShape, - { - comment: string; - } - > - >; + expect(migrations['7.14.0']).toBeDefined(); + const result = migrations['7.14.0'](caseComment, contextMock); + const parsedComment = parseCommentString(result.attributes.comment); const lensVisualizations = getLensVisualizations(parsedComment.children); diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/index.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/index.ts index b7da708ae7f07..b1792d98cfdb2 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations/index.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/index.ts @@ -8,7 +8,7 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { flow, mapValues } from 'lodash'; -import { EmbeddableSetup } from '../../../../../../src/plugins/embeddable/server'; +import { LensServerPluginSetup } from '../../../../lens/server'; import { mergeMigrationFunctionMaps, @@ -136,14 +136,14 @@ const migrateByValueLensVisualizations = ( }; export interface CreateCommentsMigrationsDeps { - embeddable: EmbeddableSetup; + lensEmbeddableFactory: LensServerPluginSetup['lensEmbeddableFactory']; } export const createCommentsMigrations = ( migrationDeps: CreateCommentsMigrationsDeps ): SavedObjectMigrationMap => { const embeddableMigrations = mapValues( - migrationDeps.embeddable.getAllMigrations(), + migrationDeps.lensEmbeddableFactory().migrations, migrateByValueLensVisualizations ) as MigrateFunctionsObject; diff --git a/x-pack/plugins/lens/common/embeddable_factory/index.ts b/x-pack/plugins/lens/common/embeddable_factory/index.ts new file mode 100644 index 0000000000000..1eaa1dddfdf08 --- /dev/null +++ b/x-pack/plugins/lens/common/embeddable_factory/index.ts @@ -0,0 +1,36 @@ +/* + * 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. + */ + +import { SerializableRecord, Serializable } from '@kbn/utility-types'; +import { SavedObjectReference } from 'src/core/types'; +import { EmbeddableStateWithType } from 'src/plugins/embeddable/common'; +import { EmbeddableRegistryDefinition } from 'src/plugins/embeddable/server'; + +export type LensEmbeddablePersistableState = EmbeddableStateWithType & { + attributes: SerializableRecord; +}; + +export const inject: EmbeddableRegistryDefinition['inject'] = (state, references) => { + const typedState = state as LensEmbeddablePersistableState; + + if ('attributes' in typedState && typedState.attributes !== undefined) { + typedState.attributes.references = (references as unknown) as Serializable[]; + } + + return typedState; +}; + +export const extract: EmbeddableRegistryDefinition['extract'] = (state) => { + let references: SavedObjectReference[] = []; + const typedState = state as LensEmbeddablePersistableState; + + if ('attributes' in typedState && typedState.attributes !== undefined) { + references = (typedState.attributes.references as unknown) as SavedObjectReference[]; + } + + return { state, references }; +}; diff --git a/x-pack/plugins/lens/public/embeddable/embeddable_factory.ts b/x-pack/plugins/lens/public/embeddable/embeddable_factory.ts index 4cc074b5e830c..dcb72455e0ee9 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable_factory.ts +++ b/x-pack/plugins/lens/public/embeddable/embeddable_factory.ts @@ -5,11 +5,10 @@ * 2.0. */ -import type { Capabilities, HttpSetup, SavedObjectReference } from 'kibana/public'; +import type { Capabilities, HttpSetup } from 'kibana/public'; import { i18n } from '@kbn/i18n'; import { RecursiveReadonly } from '@kbn/utility-types'; import { Ast } from '@kbn/interpreter/target/common'; -import { EmbeddableStateWithType } from 'src/plugins/embeddable/common'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/public'; import { IndexPatternsContract, TimefilterContract } from '../../../../../src/plugins/data/public'; import { ReactExpressionRendererType } from '../../../../../src/plugins/expressions/public'; @@ -23,6 +22,7 @@ import { Document } from '../persistence/saved_object_store'; import { LensAttributeService } from '../lens_attribute_service'; import { DOC_TYPE } from '../../common'; import { ErrorMessage } from '../editor_frame_service/types'; +import { extract, inject } from '../../common/embeddable_factory'; export interface LensEmbeddableStartServices { timefilter: TimefilterContract; @@ -112,14 +112,6 @@ export class EmbeddableFactory implements EmbeddableFactoryDefinition { ); } - extract(state: EmbeddableStateWithType) { - let references: SavedObjectReference[] = []; - const typedState = (state as unknown) as LensEmbeddableInput; - - if ('attributes' in typedState && typedState.attributes !== undefined) { - references = typedState.attributes.references; - } - - return { state, references }; - } + extract = extract; + inject = inject; } diff --git a/x-pack/plugins/lens/server/embeddable/lens_embeddable_factory.ts b/x-pack/plugins/lens/server/embeddable/lens_embeddable_factory.ts index 1324e60303366..86a3a600b58ab 100644 --- a/x-pack/plugins/lens/server/embeddable/lens_embeddable_factory.ts +++ b/x-pack/plugins/lens/server/embeddable/lens_embeddable_factory.ts @@ -7,8 +7,6 @@ import { EmbeddableRegistryDefinition } from 'src/plugins/embeddable/server'; import type { SerializableRecord } from '@kbn/utility-types'; -import { SavedObjectReference } from 'kibana/server'; -import { EmbeddableStateWithType } from 'src/plugins/embeddable/common'; import { DOC_TYPE } from '../../common'; import { commonRemoveTimezoneDateHistogramParam, @@ -21,6 +19,7 @@ import { LensDocShapePre712, VisStatePre715, } from '../migrations/types'; +import { extract, inject } from '../../common/embeddable_factory'; export const lensEmbeddableFactory = (): EmbeddableRegistryDefinition => { return { @@ -52,16 +51,7 @@ export const lensEmbeddableFactory = (): EmbeddableRegistryDefinition => { } as unknown) as SerializableRecord; }, }, - extract(state: EmbeddableStateWithType) { - let references: SavedObjectReference[] = []; - const typedState = (state as unknown) as SerializableRecord; - - if ('attributes' in typedState && typedState.attributes !== undefined) { - // @ts-expect-error - references = typedState?.attributes?.references || []; - } - - return { state, references }; - }, + extract, + inject, }; }; diff --git a/x-pack/plugins/lens/server/index.ts b/x-pack/plugins/lens/server/index.ts index 76c2d81a75d49..f8a9b2452de41 100644 --- a/x-pack/plugins/lens/server/index.ts +++ b/x-pack/plugins/lens/server/index.ts @@ -8,6 +8,7 @@ import { PluginInitializerContext, PluginConfigDescriptor } from 'kibana/server'; import { LensServerPlugin } from './plugin'; +export type { LensServerPluginSetup } from './plugin'; export * from './plugin'; export * from './migrations/types'; diff --git a/x-pack/plugins/lens/server/plugin.tsx b/x-pack/plugins/lens/server/plugin.tsx index f0ee801ece89b..e242fc8e4c5d6 100644 --- a/x-pack/plugins/lens/server/plugin.tsx +++ b/x-pack/plugins/lens/server/plugin.tsx @@ -36,7 +36,11 @@ export interface PluginStartContract { data: DataPluginStart; } -export class LensServerPlugin implements Plugin<{}, {}, {}, {}> { +export interface LensServerPluginSetup { + lensEmbeddableFactory: typeof lensEmbeddableFactory; +} + +export class LensServerPlugin implements Plugin { private readonly kibanaIndexConfig: Observable<{ kibana: { index: string } }>; private readonly telemetryLogger: Logger; @@ -63,8 +67,11 @@ export class LensServerPlugin implements Plugin<{}, {}, {}, {}> { plugins.taskManager ); } + plugins.embeddable.registerEmbeddableFactory(lensEmbeddableFactory()); - return {}; + return { + lensEmbeddableFactory, + }; } start(core: CoreStart, plugins: PluginStartContract) { diff --git a/x-pack/plugins/observability/kibana.json b/x-pack/plugins/observability/kibana.json index 2192be083fe88..4273252850da4 100644 --- a/x-pack/plugins/observability/kibana.json +++ b/x-pack/plugins/observability/kibana.json @@ -20,7 +20,6 @@ "alerting", "cases", "data", - "embeddable", "features", "ruleRegistry", "timelines", diff --git a/x-pack/plugins/security_solution/kibana.json b/x-pack/plugins/security_solution/kibana.json index a944e90b32788..6d2c09c96d658 100644 --- a/x-pack/plugins/security_solution/kibana.json +++ b/x-pack/plugins/security_solution/kibana.json @@ -15,7 +15,6 @@ "features", "taskManager", "inspector", - "lens", "licensing", "maps", "timelines", @@ -31,6 +30,7 @@ "security", "spaces", "usageCollection", + "lens", "lists", "home", "telemetry",