From 74e910afbcb1c70da80fef2b7b75956f07cdddfd Mon Sep 17 00:00:00 2001 From: Christiane Heiligers Date: Fri, 3 Sep 2021 16:34:29 -0700 Subject: [PATCH 01/18] Changes edit view to json read-only view --- .../public/lib/in_app_url.test.ts | 2 +- .../public/lib/in_app_url.ts | 2 +- .../management_section/mount_section.tsx | 3 +- .../saved_object_view.test.tsx.snap | 68 ++++ .../__snapshots__/header.test.tsx.snap | 88 +---- .../__snapshots__/inspect.test.tsx.snap | 36 ++ .../__snapshots__/intro.test.tsx.snap | 80 ----- .../not_found_errors.test.tsx.snap | 16 +- .../object_view/components/field.test.tsx | 84 ----- .../object_view/components/field.tsx | 145 -------- .../object_view/components/form.tsx | 174 ---------- .../object_view/components/header.test.tsx | 8 +- .../object_view/components/header.tsx | 37 +-- .../object_view/components/index.ts | 3 +- .../object_view/components/inspect.test.tsx | 68 ++++ .../object_view/components/inspect.tsx | 52 +++ .../object_view/components/intro.test.tsx | 23 -- .../object_view/components/intro.tsx | 33 -- .../components/not_found_errors.test.tsx | 8 +- .../components/not_found_errors.tsx | 2 +- .../saved_object_view.test.mocks.ts | 25 ++ .../object_view/saved_object_view.test.tsx | 312 ++++++++++++++++++ .../object_view/saved_object_view.tsx | 118 +++---- .../__snapshots__/table.test.tsx.snap | 2 - .../objects_table/components/table.tsx | 1 - .../saved_objects_edition_page.tsx | 20 +- .../saved_objects_table_page.tsx | 11 +- .../edit_saved_object.ts | 141 +------- 28 files changed, 682 insertions(+), 880 deletions(-) create mode 100644 src/plugins/saved_objects_management/public/management_section/object_view/__snapshots__/saved_object_view.test.tsx.snap create mode 100644 src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/inspect.test.tsx.snap delete mode 100644 src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/intro.test.tsx.snap delete mode 100644 src/plugins/saved_objects_management/public/management_section/object_view/components/field.test.tsx delete mode 100644 src/plugins/saved_objects_management/public/management_section/object_view/components/field.tsx delete mode 100644 src/plugins/saved_objects_management/public/management_section/object_view/components/form.tsx create mode 100644 src/plugins/saved_objects_management/public/management_section/object_view/components/inspect.test.tsx create mode 100644 src/plugins/saved_objects_management/public/management_section/object_view/components/inspect.tsx delete mode 100644 src/plugins/saved_objects_management/public/management_section/object_view/components/intro.test.tsx delete mode 100644 src/plugins/saved_objects_management/public/management_section/object_view/components/intro.tsx create mode 100644 src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.test.mocks.ts create mode 100644 src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.test.tsx diff --git a/src/plugins/saved_objects_management/public/lib/in_app_url.test.ts b/src/plugins/saved_objects_management/public/lib/in_app_url.test.ts index 28853f836085c..b5e5ec9dc4738 100644 --- a/src/plugins/saved_objects_management/public/lib/in_app_url.test.ts +++ b/src/plugins/saved_objects_management/public/lib/in_app_url.test.ts @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - +// TINA TODO: modify to test whatever I replace the contents of `in_app_url` with import { Capabilities } from '../../../../core/public'; import { canViewInApp } from './in_app_url'; diff --git a/src/plugins/saved_objects_management/public/lib/in_app_url.ts b/src/plugins/saved_objects_management/public/lib/in_app_url.ts index c102ec21a83d0..12b1d4a9e0850 100644 --- a/src/plugins/saved_objects_management/public/lib/in_app_url.ts +++ b/src/plugins/saved_objects_management/public/lib/in_app_url.ts @@ -7,7 +7,7 @@ */ import { Capabilities } from 'src/core/public'; - +// TODO: Modify/change or add something new if we need to to replace this method's use in saved_object_view. export function canViewInApp(uiCapabilities: Capabilities, type: string): boolean { switch (type) { case 'search': diff --git a/src/plugins/saved_objects_management/public/management_section/mount_section.tsx b/src/plugins/saved_objects_management/public/management_section/mount_section.tsx index e5aaec6fa4bbc..811202060eae5 100644 --- a/src/plugins/saved_objects_management/public/management_section/mount_section.tsx +++ b/src/plugins/saved_objects_management/public/management_section/mount_section.tsx @@ -65,12 +65,11 @@ export const mountManagementSection = async ({ - + }> diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/__snapshots__/saved_object_view.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/object_view/__snapshots__/saved_object_view.test.tsx.snap new file mode 100644 index 0000000000000..bcf128431bd5e --- /dev/null +++ b/src/plugins/saved_objects_management/public/management_section/object_view/__snapshots__/saved_object_view.test.tsx.snap @@ -0,0 +1,68 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`SavedObjectEdition should render normally 1`] = ` + + + +
+ + + + + + +`; diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/header.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/header.test.tsx.snap index c72e000e95e75..662b183444818 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/header.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/header.test.tsx.snap @@ -11,17 +11,7 @@ exports[`Intro component renders correctly 1`] = ` > - } + pageTitle="Inspect saved object" rightSideItems={ Array [ , , ] @@ -64,17 +46,7 @@ exports[`Intro component renders correctly 1`] = ` className="euiPageHeader euiPageHeader--bottomBorder euiPageHeader--responsive euiPageHeader--center" > - } + pageTitle="Inspect saved object" responsive={true} rightSideItems={ Array [ @@ -85,13 +57,9 @@ exports[`Intro component renders correctly 1`] = ` size="s" > , , ] @@ -136,17 +100,7 @@ exports[`Intro component renders correctly 1`] = `

- - Edit search - + Inspect saved object

@@ -230,15 +184,11 @@ exports[`Intro component renders correctly 1`] = ` className="euiButton__text" > - View search + View in application @@ -316,15 +266,11 @@ exports[`Intro component renders correctly 1`] = ` className="euiButton__text" > - Delete search + Delete diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/inspect.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/inspect.test.tsx.snap new file mode 100644 index 0000000000000..a49c70ec358d2 --- /dev/null +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/inspect.test.tsx.snap @@ -0,0 +1,36 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Inspect component renders correctly 1`] = ` + +`; diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/intro.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/intro.test.tsx.snap deleted file mode 100644 index 9c9349b0524c0..0000000000000 --- a/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/intro.test.tsx.snap +++ /dev/null @@ -1,80 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Intro component renders correctly 1`] = ` - - - } - > -
-
- - - - Proceed with caution! - - -
- -
- -
-
- - Modifying objects is for advanced users only. Object properties are not validated and invalid objects could cause errors, data loss, or worse. Unless someone with intimate knowledge of the code told you to be in here, you probably shouldn’t be. - -
-
-
-
-
-
-
-
-`; diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/not_found_errors.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/not_found_errors.test.tsx.snap index 4227351f8e94d..93b118ef8cccf 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/not_found_errors.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/not_found_errors.test.tsx.snap @@ -74,11 +74,11 @@ exports[`NotFoundErrors component renders correctly for index-pattern type 1`] =
- If you know what this error means, go ahead and fix it — otherwise click the delete button above. + If you know what this error means, you can use the saved objects api to fix it — otherwise click the delete button above.
@@ -164,11 +164,11 @@ exports[`NotFoundErrors component renders correctly for index-pattern-field type
- If you know what this error means, go ahead and fix it — otherwise click the delete button above. + If you know what this error means, you can use the saved objects api to fix it — otherwise click the delete button above.
@@ -254,11 +254,11 @@ exports[`NotFoundErrors component renders correctly for search type 1`] = `
- If you know what this error means, go ahead and fix it — otherwise click the delete button above. + If you know what this error means, you can use the saved objects api to fix it — otherwise click the delete button above.
@@ -336,11 +336,11 @@ exports[`NotFoundErrors component renders correctly for unknown type 1`] = `
- If you know what this error means, go ahead and fix it — otherwise click the delete button above. + If you know what this error means, you can use the saved objects api to fix it — otherwise click the delete button above.
diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/field.test.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/components/field.test.tsx deleted file mode 100644 index 1fd70c65fd9c4..0000000000000 --- a/src/plugins/saved_objects_management/public/management_section/object_view/components/field.test.tsx +++ /dev/null @@ -1,84 +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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { mount } from 'enzyme'; -import { I18nProvider } from '@kbn/i18n/react'; -import { Field } from './field'; -import { FieldState, FieldType } from '../../types'; - -describe('Field component', () => { - const mountField = (props: { - type: FieldType; - name: string; - value: any; - disabled: boolean; - state?: FieldState; - onChange: (name: string, state: FieldState) => void; - }) => - mount( - - - - ).find('Field'); - - const defaultProps = { - type: 'text' as FieldType, - name: 'field', - value: '', - disabled: false, - state: undefined, - onChange: (name: string, state: FieldState) => undefined, - }; - - it('uses the field name as the label', () => { - let mounted = mountField({ ...defaultProps, name: 'some.name' }); - expect(mounted.find('EuiFormLabel').text()).toMatchInlineSnapshot(`"some.name"`); - - mounted = mountField({ ...defaultProps, name: 'someother.name' }); - expect(mounted.find('EuiFormLabel').text()).toMatchInlineSnapshot(`"someother.name"`); - }); - - it('renders a EuiCodeEditor for json type', () => { - const mounted = mountField({ ...defaultProps, type: 'json' }); - expect(mounted.exists('EuiCodeEditor')).toEqual(true); - }); - - it('renders a EuiCodeEditor for array type', () => { - const mounted = mountField({ ...defaultProps, type: 'array' }); - expect(mounted.exists('EuiCodeEditor')).toEqual(true); - }); - - it('renders a EuiSwitch for boolean type', () => { - const mounted = mountField({ ...defaultProps, type: 'boolean' }); - expect(mounted.exists('EuiSwitch')).toEqual(true); - }); - - it('display correct label for boolean type depending on value', () => { - let mounted = mountField({ ...defaultProps, type: 'boolean', value: true }); - expect(mounted.find('EuiSwitch').text()).toMatchInlineSnapshot(`"On"`); - - mounted = mountField({ ...defaultProps, type: 'boolean', value: false }); - expect(mounted.find('EuiSwitch').text()).toMatchInlineSnapshot(`"Off"`); - }); - - it('renders a EuiFieldNumber for number type', () => { - const mounted = mountField({ ...defaultProps, type: 'number' }); - expect(mounted.exists('EuiFieldNumber')).toEqual(true); - }); - - it('renders a EuiFieldText for text type', () => { - const mounted = mountField({ ...defaultProps, type: 'text' }); - expect(mounted.exists('EuiFieldText')).toEqual(true); - }); - - it('renders a EuiFieldText as fallback', () => { - const mounted = mountField({ ...defaultProps, type: 'unknown-type' as any }); - expect(mounted.exists('EuiFieldText')).toEqual(true); - }); -}); diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/field.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/components/field.tsx deleted file mode 100644 index 2273527dd63f1..0000000000000 --- a/src/plugins/saved_objects_management/public/management_section/object_view/components/field.tsx +++ /dev/null @@ -1,145 +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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React, { PureComponent } from 'react'; -import { EuiFieldNumber, EuiFieldText, EuiFormRow, EuiSwitch, EuiCodeEditor } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { FieldState, FieldType } from '../../types'; - -interface FieldProps { - type: FieldType; - name: string; - value: any; - disabled: boolean; - state?: FieldState; - onChange: (name: string, state: FieldState) => void; -} - -export class Field extends PureComponent { - render() { - const { name } = this.props; - - return ( - - {this.renderField()} - - ); - } - - onCodeEditorChange(targetValue: any) { - const { name, onChange } = this.props; - let invalid = false; - try { - JSON.parse(targetValue); - } catch (e) { - invalid = true; - } - onChange(name, { - value: targetValue, - invalid, - }); - } - - onFieldChange(targetValue: any) { - const { name, type, onChange } = this.props; - - let newParsedValue = targetValue; - let invalid = false; - if (type === 'number') { - try { - newParsedValue = Number(newParsedValue); - } catch (e) { - invalid = true; - } - } - onChange(name, { - value: newParsedValue, - invalid, - }); - } - - renderField() { - const { type, name, state, disabled } = this.props; - const currentValue = state?.value ?? this.props.value; - - switch (type) { - case 'number': - return ( - this.onFieldChange(e.target.value)} - disabled={disabled} - data-test-subj={`savedObjects-editField-${name}`} - /> - ); - case 'boolean': - return ( - - ) : ( - - ) - } - checked={!!currentValue} - onChange={(e) => this.onFieldChange(e.target.checked)} - disabled={disabled} - data-test-subj={`savedObjects-editField-${name}`} - /> - ); - case 'json': - case 'array': - return ( -
- this.onCodeEditorChange(value)} - width="100%" - height="auto" - minLines={6} - maxLines={30} - isReadOnly={disabled} - setOptions={{ - showLineNumbers: true, - tabSize: 2, - useSoftTabs: true, - }} - editorProps={{ - $blockScrolling: Infinity, - }} - showGutter={true} - /> -
- ); - default: - return ( - this.onFieldChange(e.target.value)} - disabled={disabled} - data-test-subj={`savedObjects-editField-${name}`} - /> - ); - } - } - - private get fieldId() { - const { name } = this.props; - return `savedObjects-editField-${name}`; - } -} diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/form.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/components/form.tsx deleted file mode 100644 index 8e33e0fbdc5e2..0000000000000 --- a/src/plugins/saved_objects_management/public/management_section/object_view/components/form.tsx +++ /dev/null @@ -1,174 +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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React, { Component } from 'react'; -import { - EuiForm, - EuiFlexGroup, - EuiFlexItem, - EuiButton, - EuiButtonEmpty, - EuiSpacer, -} from '@elastic/eui'; -import { set } from '@elastic/safer-lodash-set'; -import { cloneDeep } from 'lodash'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { SavedObjectsClientContract } from '../../../../../../core/public'; -import { SavedObjectLoader } from '../../../../../saved_objects/public'; -import { Field } from './field'; -import { ObjectField, FieldState, SubmittedFormData } from '../../types'; -import { createFieldList } from '../../../lib'; -import { SavedObjectWithMetadata } from '../../../types'; - -interface FormProps { - object: SavedObjectWithMetadata; - service: SavedObjectLoader; - savedObjectsClient: SavedObjectsClientContract; - editionEnabled: boolean; - onSave: (form: SubmittedFormData) => Promise; -} - -interface FormState { - fields: ObjectField[]; - fieldStates: Record; - submitting: boolean; -} - -export class Form extends Component { - constructor(props: FormProps) { - super(props); - this.state = { - fields: [], - fieldStates: {}, - submitting: false, - }; - } - - componentDidMount() { - const { object, service } = this.props; - - const fields = createFieldList(object, service); - - this.setState({ - fields, - }); - } - - render() { - const { editionEnabled, service } = this.props; - const { fields, fieldStates, submitting } = this.state; - const isValid = this.isFormValid(); - return ( - - {fields.map((field) => ( - - ))} - - - {editionEnabled && ( - - - - - - )} - - - - - - - - - ); - } - - handleFieldChange = (name: string, newState: FieldState) => { - this.setState({ - fieldStates: { - ...this.state.fieldStates, - [name]: newState, - }, - }); - }; - - isFormValid() { - const { fieldStates } = this.state; - return !Object.values(fieldStates).some((state) => state.invalid === true); - } - - onCancel = () => { - window.history.back(); - }; - - onSubmit = async () => { - const { object, onSave } = this.props; - const { fields, fieldStates } = this.state; - - if (!this.isFormValid()) { - return; - } - - this.setState({ - submitting: true, - }); - - const source = cloneDeep(object.attributes as any); - fields.forEach((field) => { - let value = fieldStates[field.name]?.value ?? field.value; - - if (field.type === 'array' && typeof value === 'string') { - value = JSON.parse(value); - } - - set(source, field.name, value); - }); - - // we extract the `references` field that does not belong to attributes - const { references, ...attributes } = source; - - await onSave({ attributes, references }); - - this.setState({ - submitting: false, - }); - }; -} diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/header.test.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/components/header.test.tsx index dbbd2485096f9..4a5c859fabf1d 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/components/header.test.tsx +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/header.test.tsx @@ -47,13 +47,13 @@ describe('Intro component', () => { ...defaultProps, canEdit: true, }); - expect(mounted.find('h1').text()).toMatchInlineSnapshot(`"Edit search"`); + expect(mounted.find('h1').text()).toMatchInlineSnapshot(`"Inspect saved object"`); mounted = mountHeader({ ...defaultProps, canEdit: false, }); - expect(mounted.find('h1').text()).toMatchInlineSnapshot(`"View search"`); + expect(mounted.find('h1').text()).toMatchInlineSnapshot(`"Inspect saved object"`); }); it('displays correct title depending on type', () => { @@ -61,13 +61,13 @@ describe('Intro component', () => { ...defaultProps, type: 'some-type', }); - expect(mounted.find('h1').text()).toMatchInlineSnapshot(`"Edit some-type"`); + expect(mounted.find('h1').text()).toMatchInlineSnapshot(`"Inspect saved object"`); mounted = mountHeader({ ...defaultProps, type: 'another-type', }); - expect(mounted.find('h1').text()).toMatchInlineSnapshot(`"Edit another-type"`); + expect(mounted.find('h1').text()).toMatchInlineSnapshot(`"Inspect saved object"`); }); it('only displays delete button if canDelete is true', () => { diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/header.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/components/header.tsx index 9a13a1d232cb3..f6d8a33eb1a34 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/components/header.tsx +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/header.tsx @@ -9,43 +9,22 @@ import React from 'react'; import { EuiButton, EuiPageHeader } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; interface HeaderProps { - canEdit: boolean; canDelete: boolean; canViewInApp: boolean; - type: string; viewUrl: string; onDeleteClick: () => void; } -const renderConditionalTitle = (canEdit: boolean, type: string) => - canEdit ? ( - - ) : ( - - ); - -export const Header = ({ - canEdit, - canDelete, - canViewInApp, - type, - viewUrl, - onDeleteClick, -}: HeaderProps) => { +export const Header = ({ canDelete, canViewInApp, viewUrl, onDeleteClick }: HeaderProps) => { return ( ), @@ -71,8 +49,7 @@ export const Header = ({ > ), diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/index.ts b/src/plugins/saved_objects_management/public/management_section/object_view/components/index.ts index ffffd589d5ef8..55322afb3fabb 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/components/index.ts +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/index.ts @@ -7,6 +7,5 @@ */ export { Header } from './header'; +export { Inspect } from './inspect'; export { NotFoundErrors } from './not_found_errors'; -export { Intro } from './intro'; -export { Form } from './form'; diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/inspect.test.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/components/inspect.test.tsx new file mode 100644 index 0000000000000..446b8f669acab --- /dev/null +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/inspect.test.tsx @@ -0,0 +1,68 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { ShallowWrapper } from 'enzyme'; +import { shallowWithI18nProvider } from '@kbn/test/jest'; +import { Inspect, InspectProps } from './inspect'; +import { SavedObjectWithMetadata } from '../../../../common'; + +describe('Inspect component', () => { + let defaultProps: { object: SavedObjectWithMetadata }; + const shallowRender = (overrides: Partial = {}) => { + return (shallowWithI18nProvider( + + ) as unknown) as ShallowWrapper; + }; + beforeEach(() => { + defaultProps = { + object: { + id: '1', + type: 'index-pattern', + attributes: { + title: `MyIndexPattern*`, + }, + meta: { + title: `MyIndexPattern*`, + icon: 'indexPatternApp', + editUrl: '#/management/kibana/indexPatterns/patterns/1', + inAppUrl: { + path: '/management/kibana/indexPatterns/patterns/1', + uiCapabilitiesPath: 'management.kibana.indexPatterns', + }, + }, + references: [], + }, + }; + }); + + it('renders correctly', async () => { + const component = shallowRender(); + await new Promise((resolve) => process.nextTick(resolve)); + component.update(); + expect(component).toMatchSnapshot(); + }); + + it("does not include `meta` in the value that's rendered", async () => { + const component = shallowRender(); + await new Promise((resolve) => process.nextTick(resolve)); + component.update(); + const codeEditorComponent = component.find('CodeEditor'); + // find could return nothing + const editorValue = codeEditorComponent + ? ((codeEditorComponent.prop('value') as unknown) as string) + : ''; + // we assert against the expected object props rather than asserting that 'meta' is removed + expect(Object.keys(JSON.parse(editorValue))).toEqual([ + 'id', + 'type', + 'attributes', + 'references', + ]); + }); +}); diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/inspect.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/components/inspect.tsx new file mode 100644 index 0000000000000..ac47528bc6d20 --- /dev/null +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/inspect.tsx @@ -0,0 +1,52 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { FC } from 'react'; +import { i18n } from '@kbn/i18n'; +import { XJsonLang } from '@kbn/monaco'; +import { omit } from 'lodash'; +import { CodeEditor } from '../../../../../kibana_react/public'; +import { SavedObjectWithMetadata } from '../../../../common'; + +export interface InspectProps { + object: SavedObjectWithMetadata; +} + +export const Inspect: FC = ({ object }) => { + const objectAsJsonString = JSON.stringify(omit(object, 'meta'), null, 2); + return ( + {}} + aria-label={i18n.translate('savedObjectsManagement.view.inspectCodeEditorAriaLabel', { + defaultMessage: 'inspect { title } object', + values: { + title: object.meta.title, + }, + })} + height={'100%'} + options={{ + automaticLayout: false, + fontSize: 12, + lineNumbers: 'on', + minimap: { + enabled: false, + }, + overviewRulerBorder: false, + readOnly: true, + scrollbar: { + alwaysConsumeMouseWheel: false, + }, + scrollBeyondLastLine: false, + wordWrap: 'on', + wrappingIndent: 'indent', + }} + /> + ); +}; diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/intro.test.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/components/intro.test.tsx deleted file mode 100644 index 0b869743f03c7..0000000000000 --- a/src/plugins/saved_objects_management/public/management_section/object_view/components/intro.test.tsx +++ /dev/null @@ -1,23 +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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { mount } from 'enzyme'; -import { I18nProvider } from '@kbn/i18n/react'; -import { Intro } from './intro'; - -describe('Intro component', () => { - it('renders correctly', () => { - const mounted = mount( - - - - ); - expect(mounted.find('Intro')).toMatchSnapshot(); - }); -}); diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/intro.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/components/intro.tsx deleted file mode 100644 index 0431208d34ad5..0000000000000 --- a/src/plugins/saved_objects_management/public/management_section/object_view/components/intro.tsx +++ /dev/null @@ -1,33 +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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { EuiCallOut } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; - -export const Intro = () => { - return ( - - } - iconType="alert" - color="warning" - > -
- -
-
- ); -}; diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/not_found_errors.test.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/components/not_found_errors.test.tsx index 767cc1ac59f47..38504b276a733 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/components/not_found_errors.test.tsx +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/not_found_errors.test.tsx @@ -23,7 +23,7 @@ describe('NotFoundErrors component', () => { const mounted = mountError('search'); expect(mounted).toMatchSnapshot(); expect(mounted.text()).toMatchInlineSnapshot( - `"There is a problem with this saved objectThe saved search associated with this object no longer exists.If you know what this error means, go ahead and fix it — otherwise click the delete button above."` + `"There is a problem with this saved objectThe saved search associated with this object no longer exists.If you know what this error means, you can use the saved objects api to fix it — otherwise click the delete button above."` ); }); @@ -31,7 +31,7 @@ describe('NotFoundErrors component', () => { const mounted = mountError('index-pattern'); expect(mounted).toMatchSnapshot(); expect(mounted.text()).toMatchInlineSnapshot( - `"There is a problem with this saved objectThe index pattern associated with this object no longer exists.If you know what this error means, go ahead and fix it — otherwise click the delete button above."` + `"There is a problem with this saved objectThe index pattern associated with this object no longer exists.If you know what this error means, you can use the saved objects api to fix it — otherwise click the delete button above."` ); }); @@ -39,7 +39,7 @@ describe('NotFoundErrors component', () => { const mounted = mountError('index-pattern-field'); expect(mounted).toMatchSnapshot(); expect(mounted.text()).toMatchInlineSnapshot( - `"There is a problem with this saved objectA field associated with this object no longer exists in the index pattern.If you know what this error means, go ahead and fix it — otherwise click the delete button above."` + `"There is a problem with this saved objectA field associated with this object no longer exists in the index pattern.If you know what this error means, you can use the saved objects api to fix it — otherwise click the delete button above."` ); }); @@ -47,7 +47,7 @@ describe('NotFoundErrors component', () => { const mounted = mountError('unknown'); expect(mounted).toMatchSnapshot(); expect(mounted.text()).toMatchInlineSnapshot( - `"There is a problem with this saved objectIf you know what this error means, go ahead and fix it — otherwise click the delete button above."` + `"There is a problem with this saved objectIf you know what this error means, you can use the saved objects api to fix it — otherwise click the delete button above."` ); }); }); diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/not_found_errors.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/components/not_found_errors.tsx index 2bce7b387a7e4..2a59e9d01093a 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/components/not_found_errors.tsx +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/not_found_errors.tsx @@ -58,7 +58,7 @@ export const NotFoundErrors = ({ type }: NotFoundErrors) => {
diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.test.mocks.ts b/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.test.mocks.ts new file mode 100644 index 0000000000000..7243955100690 --- /dev/null +++ b/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.test.mocks.ts @@ -0,0 +1,25 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +jest.doMock('lodash', () => { + const original = jest.requireActual('lodash'); + return { + ...original, + get: (func: Function) => { + function get(this: any, args: any[]) { + return func.apply(this, args); + } + return get; + }, + }; +}); + +export const bulkGetObjectsMock = jest.fn(); +jest.doMock('../../lib/bulk_get_objects', () => ({ + bulkGetObjects: bulkGetObjectsMock, +})); diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.test.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.test.tsx new file mode 100644 index 0000000000000..776358ae25adb --- /dev/null +++ b/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.test.tsx @@ -0,0 +1,312 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +// TODO: Add test using CodeEditor mock. Do a search for how the editor is mocked out elsewhere in the code. +// See https://github.com/elastic/kibana/pull/101392/files as example setup with extra benefit of 'copy-to-clickboard' action. +// also see src/plugins/discover/public/application/components/source_viewer/source_viewer.test.tsx as example for full mount with the editor +import { bulkGetObjectsMock } from './saved_object_view.test.mocks'; + +import React from 'react'; +import { ShallowWrapper } from 'enzyme'; +import { shallowWithI18nProvider } from '@kbn/test/jest'; + +import { + httpServiceMock, + overlayServiceMock, + notificationServiceMock, + savedObjectsServiceMock, + applicationServiceMock, + uiSettingsServiceMock, + scopedHistoryMock, +} from '../../../../../core/public/mocks'; + +import { + SavedObjectEdition, + SavedObjectEditionProps, + SavedObjectEditionState, +} from './saved_object_view'; + +describe('SavedObjectEdition', () => { + let defaultProps: SavedObjectEditionProps; + let http: ReturnType; + let overlays: ReturnType; + let notifications: ReturnType; + let savedObjects: ReturnType; + let uiSettings: ReturnType; + let history: ReturnType; + let applications: ReturnType; + + const shallowRender = (overrides: Partial = {}) => { + return (shallowWithI18nProvider( + + ) as unknown) as ShallowWrapper< + SavedObjectEditionProps, + SavedObjectEditionState, + SavedObjectEdition + >; + }; + + beforeEach(() => { + http = httpServiceMock.createStartContract(); + overlays = overlayServiceMock.createStartContract(); + notifications = notificationServiceMock.createStartContract(); + savedObjects = savedObjectsServiceMock.createStartContract(); + uiSettings = uiSettingsServiceMock.createStartContract(); + history = scopedHistoryMock.create(); + applications = applicationServiceMock.createStartContract(); + applications.capabilities = { + navLinks: {}, + management: {}, + catalogue: {}, + savedObjectsManagement: { + read: true, + edit: false, + delete: false, + }, + }; + + http.post.mockResolvedValue([]); + + defaultProps = { + id: '1', + savedObjectType: 'index-pattern', + http, + capabilities: applications.capabilities, + overlays, + notifications, + savedObjectsClient: savedObjects.client, + history, + uiSettings, + }; + + bulkGetObjectsMock.mockImplementation(() => [{}]); + }); + + it('should render normally', async () => { + bulkGetObjectsMock.mockImplementation(() => + Promise.resolve([ + { + id: '1', + type: 'index-pattern', + attributes: { + title: `MyIndexPattern*`, + }, + meta: { + title: `MyIndexPattern*`, + icon: 'indexPatternApp', + editUrl: '#/management/kibana/indexPatterns/patterns/1', + inAppUrl: { + path: '/management/kibana/indexPatterns/patterns/1', + uiCapabilitiesPath: 'management.kibana.indexPatterns', + }, + }, + }, + ]) + ); + const component = shallowRender(); + // Ensure all promises resolve + await new Promise((resolve) => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); + expect(component).toMatchSnapshot(); + }); + + it('should add danger toast when bulk get fails', async () => { + bulkGetObjectsMock.mockImplementation(() => + Promise.resolve([ + { + error: { + message: 'Not found', + }, + }, + ]) + ); + const component = shallowRender({ notFoundType: 'does_not_exist' }); + await new Promise((resolve) => process.nextTick(resolve)); + component.update(); + + expect(notifications.toasts.addDanger).toHaveBeenCalled(); + }); + + it('should add danger toast when bulk get throws', async () => { + bulkGetObjectsMock.mockImplementation(() => Promise.reject(new Error('fail'))); + const component = shallowRender({ notFoundType: 'does_not_exist' }); + await new Promise((resolve) => process.nextTick(resolve)); + component.update(); + + expect(notifications.toasts.addDanger).toHaveBeenCalled(); + }); + + it('should pass the correct props to the child components', async () => { + const savedObjectItem = { + id: '1', + type: 'index-pattern', + attributes: { + title: `MyIndexPattern*`, + }, + meta: { + title: `MyIndexPattern*`, + icon: 'indexPatternApp', + editUrl: '#/management/kibana/indexPatterns/patterns/1', + inAppUrl: { + path: '/management/kibana/indexPatterns/patterns/1', + uiCapabilitiesPath: 'management.kibana.indexPatterns', + }, + hiddenType: false, + }, + }; + bulkGetObjectsMock.mockImplementation(() => Promise.resolve([savedObjectItem])); + applications.capabilities = { + navLinks: {}, + management: {}, + catalogue: {}, + savedObjectsManagement: { + read: true, + edit: false, + delete: true, + }, + }; + const component = shallowRender({ + capabilities: applications.capabilities, + }); + await new Promise((resolve) => process.nextTick(resolve)); + component.update(); + const headerComponent = component.find('Header'); + expect(headerComponent.prop('canViewInApp')).toBe(true); + expect(headerComponent.prop('canDelete')).toBe(true); + expect(headerComponent.prop('viewUrl')).toEqual('/management/kibana/indexPatterns/patterns/1'); + const inspectComponent = component.find('Inspect'); + expect(inspectComponent.prop('object')).toEqual(savedObjectItem); + }); + + it("does not render Inspect if there isn't an object", async () => { + bulkGetObjectsMock.mockImplementation(() => Promise.resolve([])); + applications.capabilities = { + navLinks: {}, + management: {}, + catalogue: {}, + savedObjectsManagement: { + read: true, + edit: false, + delete: true, + }, + }; + const component = shallowRender({ + capabilities: applications.capabilities, + }); + await new Promise((resolve) => process.nextTick(resolve)); + component.update(); + const inspectComponent = component.find('Inspect'); + expect(inspectComponent).toEqual({}); + }); + + describe('delete', () => { + const savedObjectItem = { + id: '1', + type: 'index-pattern', + attributes: { + title: `MyIndexPattern*`, + }, + meta: { + title: `MyIndexPattern*`, + icon: 'indexPatternApp', + editUrl: '#/management/kibana/indexPatterns/patterns/1', + inAppUrl: { + path: '/management/kibana/indexPatterns/patterns/1', + uiCapabilitiesPath: 'management.kibana.indexPatterns', + }, + hiddenType: false, + }, + }; + + it('should display a confirmation message on deleting the saved object', async () => { + bulkGetObjectsMock.mockImplementation(() => Promise.resolve([savedObjectItem])); + const mockSavedObjectsClient = { + ...defaultProps.savedObjectsClient, + delete: jest.fn().mockImplementation(() => ({})), + }; + applications.capabilities = { + navLinks: {}, + management: {}, + catalogue: {}, + savedObjectsManagement: { + read: true, + edit: false, + delete: true, + }, + }; + overlays.openConfirm.mockResolvedValue(false); + const component = shallowRender({ + capabilities: applications.capabilities, + savedObjectsClient: mockSavedObjectsClient, + overlays, + }); + await new Promise((resolve) => process.nextTick(resolve)); + component.update(); + component.instance().delete(); + expect(overlays.openConfirm).toHaveBeenCalledWith( + 'This action permanently removes the object from Kibana.', + { + buttonColor: 'danger', + confirmButtonText: 'Delete', + title: `Delete '${savedObjectItem.meta.title}'?`, + } + ); + }); + + it('should route back if action is confirm and user accepted', async () => { + bulkGetObjectsMock.mockImplementation(() => Promise.resolve([savedObjectItem])); + const mockSavedObjectsClient = { + ...defaultProps.savedObjectsClient, + delete: jest.fn().mockImplementation(() => ({})), + }; + applications.capabilities = { + navLinks: {}, + management: {}, + catalogue: {}, + savedObjectsManagement: { + read: true, + edit: false, + delete: true, + }, + }; + overlays.openConfirm.mockResolvedValue(true); + const component = shallowRender({ + capabilities: applications.capabilities, + savedObjectsClient: mockSavedObjectsClient, + overlays, + }); + await new Promise((resolve) => process.nextTick(resolve)); + component.update(); + component.instance().delete(); + expect(overlays.openConfirm).toHaveBeenCalledTimes(1); + expect(history.location.pathname).toEqual('/'); + }); + + it('should not enable delete if the saved object is hidden', async () => { + bulkGetObjectsMock.mockImplementation(() => + Promise.resolve([{ ...savedObjectItem, meta: { hiddenType: true } }]) + ); + applications.capabilities = { + navLinks: {}, + management: {}, + catalogue: {}, + savedObjectsManagement: { + read: true, + edit: false, + delete: true, + }, + }; + const component = shallowRender({ + capabilities: applications.capabilities, + }); + await new Promise((resolve) => process.nextTick(resolve)); + component.update(); + expect(component.find('Header').prop('canDelete')).toBe(false); + }); + }); +}); diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.tsx index 3bf70de1abdad..eb315a27d7197 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.tsx +++ b/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.tsx @@ -8,7 +8,9 @@ import React, { Component } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiSpacer } from '@elastic/eui'; +import { EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; +import { get } from 'lodash'; +import { KibanaContextProvider } from '../../../../kibana_react/public'; import { Capabilities, SavedObjectsClientContract, @@ -16,27 +18,25 @@ import { NotificationsStart, ScopedHistory, HttpSetup, + IUiSettingsClient, } from '../../../../../core/public'; -import { ISavedObjectsManagementServiceRegistry } from '../../services'; -import { Header, NotFoundErrors, Intro, Form } from './components'; -import { canViewInApp, bulkGetObjects } from '../../lib'; -import { SubmittedFormData } from '../types'; +import { Header, Inspect, NotFoundErrors } from './components'; +import { bulkGetObjects } from '../../lib/bulk_get_objects'; import { SavedObjectWithMetadata } from '../../types'; -interface SavedObjectEditionProps { +export interface SavedObjectEditionProps { id: string; + savedObjectType: string; http: HttpSetup; - serviceName: string; - serviceRegistry: ISavedObjectsManagementServiceRegistry; capabilities: Capabilities; overlays: OverlayStart; notifications: NotificationsStart; notFoundType?: string; savedObjectsClient: SavedObjectsClientContract; history: ScopedHistory; + uiSettings: IUiSettingsClient; } - -interface SavedObjectEditionState { +export interface SavedObjectEditionState { type: string; object?: SavedObjectWithMetadata; } @@ -45,7 +45,6 @@ const unableFindSavedObjectNotificationMessage = i18n.translate( 'savedObjectsManagement.objectView.unableFindSavedObjectNotificationMessage', { defaultMessage: 'Unable to find saved object' } ); - export class SavedObjectEdition extends Component< SavedObjectEditionProps, SavedObjectEditionState @@ -53,8 +52,7 @@ export class SavedObjectEdition extends Component< constructor(props: SavedObjectEditionProps) { super(props); - const { serviceRegistry, serviceName } = props; - const type = serviceRegistry.get(serviceName)!.service.type; + const { savedObjectType: type } = props; this.state = { object: undefined, @@ -85,60 +83,45 @@ export class SavedObjectEdition extends Component< }); } + canViewInApp = (capabilities: Capabilities, obj?: SavedObjectWithMetadata) => { + return obj && obj.meta.inAppUrl + ? get(capabilities, obj?.meta.inAppUrl?.uiCapabilitiesPath, false) && + Boolean(obj?.meta.inAppUrl?.path) + : false; + }; + render() { - const { - capabilities, - notFoundType, - serviceRegistry, - http, - serviceName, - savedObjectsClient, - } = this.props; - const { type } = this.state; + const { capabilities, notFoundType, http, uiSettings } = this.props; const { object } = this.state; - const { edit: canEdit, delete: canDelete } = capabilities.savedObjectsManagement as Record< - string, - boolean - >; - const canView = canViewInApp(capabilities, type) && Boolean(object?.meta.inAppUrl?.path); - const service = serviceRegistry.get(serviceName)!.service; - + const { delete: canDelete } = capabilities.savedObjectsManagement as Record; + const canView = this.canViewInApp(capabilities, object); return ( -
-
this.delete()} - viewUrl={http.basePath.prepend(object?.meta.inAppUrl?.path || '')} - /> - - {notFoundType && ( - <> - - - - )} - {canEdit && ( - <> - - - - )} - {object && ( - <> - -
+ + +
this.delete()} + viewUrl={http.basePath.prepend(object?.meta.inAppUrl?.path || '')} /> - - )} -
+ + {notFoundType && ( + + + + )} + {object && ( + + + + )} + + ); } @@ -173,15 +156,6 @@ export class SavedObjectEdition extends Component< } } - saveChanges = async ({ attributes, references }: SubmittedFormData) => { - const { savedObjectsClient, notifications } = this.props; - const { object, type } = this.state; - - await savedObjectsClient.update(object!.type, object!.id, attributes, { references }); - notifications.toasts.addSuccess(`Updated '${attributes.title}' ${type} object`); - this.redirectToListing(); - }; - redirectToListing() { this.props.history.push('/'); } diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/table.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/table.test.tsx.snap index bb426c91e827c..8325e7dc886e8 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/table.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/table.test.tsx.snap @@ -146,7 +146,6 @@ exports[`Table prevents saved objects from being deleted 1`] = ` Object { "actions": Array [ Object { - "available": [Function], "data-test-subj": "savedObjectsTableAction-inspect", "description": "Inspect this saved object", "icon": "inspect", @@ -362,7 +361,6 @@ exports[`Table should render normally 1`] = ` Object { "actions": Array [ Object { - "available": [Function], "data-test-subj": "savedObjectsTableAction-inspect", "description": "Inspect this saved object", "icon": "inspect", diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.tsx index 4e4bd51c4bb84..0645c0955f7ac 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.tsx @@ -243,7 +243,6 @@ export class Table extends PureComponent { type: 'icon', icon: 'inspect', onClick: (object) => goInspectObject(object), - available: (object) => !!object.meta.editUrl, 'data-test-subj': 'savedObjectsTableAction-inspect', }, { diff --git a/src/plugins/saved_objects_management/public/management_section/saved_objects_edition_page.tsx b/src/plugins/saved_objects_management/public/management_section/saved_objects_edition_page.tsx index f52ac64377c36..98c6f0fe8de40 100644 --- a/src/plugins/saved_objects_management/public/management_section/saved_objects_edition_page.tsx +++ b/src/plugins/saved_objects_management/public/management_section/saved_objects_edition_page.tsx @@ -12,26 +12,22 @@ import { parse } from 'query-string'; import { i18n } from '@kbn/i18n'; import { CoreStart, ChromeBreadcrumb, ScopedHistory } from 'src/core/public'; import { RedirectAppLinks } from '../../../kibana_react/public'; -import { ISavedObjectsManagementServiceRegistry } from '../services'; import { SavedObjectEdition } from './object_view'; const SavedObjectsEditionPage = ({ coreStart, - serviceRegistry, setBreadcrumbs, history, }: { coreStart: CoreStart; - serviceRegistry: ISavedObjectsManagementServiceRegistry; setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => void; history: ScopedHistory; }) => { - const { service: serviceName, id } = useParams<{ service: string; id: string }>(); + const { type, id } = useParams<{ type: string; id: string }>(); const capabilities = coreStart.application.capabilities; const { search } = useLocation(); const query = parse(search); - const service = serviceRegistry.get(serviceName); useEffect(() => { setBreadcrumbs([ @@ -42,26 +38,26 @@ const SavedObjectsEditionPage = ({ href: '/', }, { - text: i18n.translate('savedObjectsManagement.breadcrumb.edit', { - defaultMessage: 'Edit {savedObjectType}', - values: { savedObjectType: service?.service.type ?? 'object' }, + text: i18n.translate('savedObjectsManagement.breadcrumb.inspect', { + defaultMessage: 'Inspect {savedObjectType}', + values: { savedObjectType: type }, }), }, ]); - }, [setBreadcrumbs, service]); + }, [setBreadcrumbs, type]); return ( - + diff --git a/src/plugins/saved_objects_management/public/management_section/saved_objects_table_page.tsx b/src/plugins/saved_objects_management/public/management_section/saved_objects_table_page.tsx index f22f0333ec229..b6ca2a9da3489 100644 --- a/src/plugins/saved_objects_management/public/management_section/saved_objects_table_page.tsx +++ b/src/plugins/saved_objects_management/public/management_section/saved_objects_table_page.tsx @@ -94,12 +94,11 @@ const SavedObjectsTablePage = ({ applications={coreStart.application} perPageConfig={itemsPerPage} goInspectObject={(savedObject) => { - const { editUrl } = savedObject.meta; - if (editUrl) { - return coreStart.application.navigateToUrl( - coreStart.http.basePath.prepend(`/app${editUrl}`) - ); - } + coreStart.application.navigateToUrl( + coreStart.http.basePath.prepend( + `/app/management/kibana/objects/${savedObject.type}/${savedObject.id}` + ) + ); }} canGoInApp={(savedObject) => { const { inAppUrl } = savedObject.meta; diff --git a/test/functional/apps/saved_objects_management/edit_saved_object.ts b/test/functional/apps/saved_objects_management/edit_saved_object.ts index f4bf45c0b7f70..1458559d015fe 100644 --- a/test/functional/apps/saved_objects_management/edit_saved_object.ts +++ b/test/functional/apps/saved_objects_management/edit_saved_object.ts @@ -18,34 +18,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const browser = getService('browser'); const find = getService('find'); - const setFieldValue = async (fieldName: string, value: string) => { - return testSubjects.setValue(`savedObjects-editField-${fieldName}`, value); - }; - - const getFieldValue = async (fieldName: string) => { - return testSubjects.getAttribute(`savedObjects-editField-${fieldName}`, 'value'); - }; - - const setAceEditorFieldValue = async (fieldName: string, fieldValue: string) => { - const editorId = `savedObjects-editField-${fieldName}-aceEditor`; - await find.clickByCssSelector(`#${editorId}`); - return browser.execute( - (editor: string, value: string) => { - return (window as any).ace.edit(editor).setValue(value); - }, - editorId, - fieldValue - ); - }; - - const getAceEditorFieldValue = async (fieldName: string) => { - const editorId = `savedObjects-editField-${fieldName}-aceEditor`; - await find.clickByCssSelector(`#${editorId}`); - return browser.execute((editor: string) => { - return (window as any).ace.edit(editor).getValue() as string; - }, editorId); - }; - const focusAndClickButton = async (buttonSubject: string) => { const button = await testSubjects.find(buttonSubject); await button.scrollIntoViewIfNecessary(); @@ -70,113 +42,34 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ); }); - it('allows to update the saved object when submitting', async () => { + it('allows to view the saved object', async () => { await PageObjects.settings.navigateTo(); await PageObjects.settings.clickKibanaSavedObjects(); - - let objects = await PageObjects.savedObjects.getRowTitles(); + const objects = await PageObjects.savedObjects.getRowTitles(); expect(objects.includes('A Dashboard')).to.be(true); - - await PageObjects.common.navigateToUrl( - 'management', - 'kibana/objects/savedDashboards/i-exist', - { - shouldUseHashForSubUrl: false, - } - ); - - await testSubjects.existOrFail('savedObjectEditSave'); - - expect(await getFieldValue('title')).to.eql('A Dashboard'); - - await setFieldValue('title', 'Edited Dashboard'); - await setFieldValue('description', 'Some description'); - - await focusAndClickButton('savedObjectEditSave'); - - objects = await PageObjects.savedObjects.getRowTitles(); - expect(objects.includes('A Dashboard')).to.be(false); - expect(objects.includes('Edited Dashboard')).to.be(true); - - await PageObjects.common.navigateToUrl( - 'management', - 'kibana/objects/savedDashboards/i-exist', - { - shouldUseHashForSubUrl: false, - } - ); - - expect(await getFieldValue('title')).to.eql('Edited Dashboard'); - expect(await getFieldValue('description')).to.eql('Some description'); + await PageObjects.common.navigateToUrl('management', 'kibana/objects/dashboard/i-exist', { + shouldUseHashForSubUrl: false, + }); + const inspectContainer = await find.byClassName('kibanaCodeEditor'); + const visibleContainerText = await inspectContainer.getVisibleText(); + // ensure that something renders visibly + expect(visibleContainerText.includes('A Dashboard')); }); it('allows to delete a saved object', async () => { - await PageObjects.common.navigateToUrl( - 'management', - 'kibana/objects/savedDashboards/i-exist', - { - shouldUseHashForSubUrl: false, - } - ); - - await focusAndClickButton('savedObjectEditDelete'); - await PageObjects.common.clickConfirmOnModal(); - - const objects = await PageObjects.savedObjects.getRowTitles(); - expect(objects.includes('A Dashboard')).to.be(false); - }); - - it('preserves the object references when saving', async () => { - const testVisualizationUrl = - 'kibana/objects/savedVisualizations/75c3e060-1e7c-11e9-8488-65449e65d0ed'; - const visualizationRefs = [ - { - name: 'kibanaSavedObjectMeta.searchSourceJSON.index', - type: 'index-pattern', - id: 'logstash-*', - }, - ]; - await PageObjects.settings.navigateTo(); await PageObjects.settings.clickKibanaSavedObjects(); - - const objects = await PageObjects.savedObjects.getRowTitles(); - expect(objects.includes('A Pie')).to.be(true); - - await PageObjects.common.navigateToUrl('management', testVisualizationUrl, { - shouldUseHashForSubUrl: false, - }); - - await testSubjects.existOrFail('savedObjectEditSave'); - - let displayedReferencesValue = await getAceEditorFieldValue('references'); - - expect(JSON.parse(displayedReferencesValue)).to.eql(visualizationRefs); - - await focusAndClickButton('savedObjectEditSave'); - - await PageObjects.savedObjects.getRowTitles(); - - await PageObjects.common.navigateToUrl('management', testVisualizationUrl, { - shouldUseHashForSubUrl: false, - }); - - // Parsing to avoid random keys ordering issues in raw string comparison - expect(JSON.parse(await getAceEditorFieldValue('references'))).to.eql(visualizationRefs); - - await setAceEditorFieldValue('references', JSON.stringify([], undefined, 2)); - - await focusAndClickButton('savedObjectEditSave'); - - await PageObjects.savedObjects.getRowTitles(); - - await PageObjects.common.navigateToUrl('management', testVisualizationUrl, { + let objects = await PageObjects.savedObjects.getRowTitles(); + expect(objects.includes('A Dashboard')).to.be(true); + await PageObjects.savedObjects.clickInspectByTitle('A Dashboard'); + await PageObjects.common.navigateToUrl('management', 'kibana/objects/dashboard/i-exist', { shouldUseHashForSubUrl: false, }); + await focusAndClickButton('savedObjectEditDelete'); + await PageObjects.common.clickConfirmOnModal(); - displayedReferencesValue = await getAceEditorFieldValue('references'); - - expect(JSON.parse(displayedReferencesValue)).to.eql([]); + objects = await PageObjects.savedObjects.getRowTitles(); + expect(objects.includes('A Dashboard')).to.be(false); }); }); } From 0b257602e7f9c31f0c855581bd07a43b1703390f Mon Sep 17 00:00:00 2001 From: Christiane Heiligers Date: Sun, 12 Sep 2021 17:24:48 -0700 Subject: [PATCH 02/18] Adds copy to clipboard functionality --- .../saved_object_view.test.tsx.snap | 1 + .../object_view/components/inspect.test.tsx | 3 +- .../object_view/components/inspect.tsx | 90 +++++++++++++------ .../edit_saved_object.ts | 1 - 4 files changed, 64 insertions(+), 31 deletions(-) diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/__snapshots__/saved_object_view.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/object_view/__snapshots__/saved_object_view.test.tsx.snap index bcf128431bd5e..54775a2d2ac36 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/__snapshots__/saved_object_view.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/object_view/__snapshots__/saved_object_view.test.tsx.snap @@ -43,6 +43,7 @@ exports[`SavedObjectEdition should render normally 1`] = ` grow={true} > { const component = shallowRender(); await new Promise((resolve) => process.nextTick(resolve)); component.update(); - expect(component).toMatchSnapshot(); + const codeEditorComponent = component.find('CodeEditor'); + expect(codeEditorComponent).toMatchSnapshot(); }); it("does not include `meta` in the value that's rendered", async () => { diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/inspect.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/components/inspect.tsx index ac47528bc6d20..27ebf39642853 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/components/inspect.tsx +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/inspect.tsx @@ -10,43 +10,75 @@ import React, { FC } from 'react'; import { i18n } from '@kbn/i18n'; import { XJsonLang } from '@kbn/monaco'; import { omit } from 'lodash'; +import { EuiButtonEmpty, EuiCopy, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { CodeEditor } from '../../../../../kibana_react/public'; import { SavedObjectWithMetadata } from '../../../../common'; export interface InspectProps { object: SavedObjectWithMetadata; } - +const codeEditorAriaLabel = (title: string) => + i18n.translate('savedObjectsManagement.view.inspectCodeEditorAriaLabel', { + defaultMessage: 'inspect { title } object', + values: { + title, + }, + }); +const copyToClipboardLabel = i18n.translate('savedObjectsManagement.view.copyToClipboardLabel', { + defaultMessage: 'Copy to clipboard', +}); +const copyToClipboardAriaLabel = i18n.translate( + 'savedObjectsManagement.view.copyToClipboardAriaLabel', + { + defaultMessage: 'Copy to clipboard', + } +); export const Inspect: FC = ({ object }) => { + const title = object.meta.title || 'saved object'; const objectAsJsonString = JSON.stringify(omit(object, 'meta'), null, 2); return ( - {}} - aria-label={i18n.translate('savedObjectsManagement.view.inspectCodeEditorAriaLabel', { - defaultMessage: 'inspect { title } object', - values: { - title: object.meta.title, - }, - })} - height={'100%'} - options={{ - automaticLayout: false, - fontSize: 12, - lineNumbers: 'on', - minimap: { - enabled: false, - }, - overviewRulerBorder: false, - readOnly: true, - scrollbar: { - alwaysConsumeMouseWheel: false, - }, - scrollBeyondLastLine: false, - wordWrap: 'on', - wrappingIndent: 'indent', - }} - /> + + +
+ + {(copy) => ( + + {copyToClipboardLabel} + + )} + + +
+ {}} + aria-label={codeEditorAriaLabel(title)} + height={'100%'} + options={{ + automaticLayout: false, + fontSize: 12, + lineNumbers: 'on', + minimap: { + enabled: false, + }, + overviewRulerBorder: false, + readOnly: true, + scrollbar: { + alwaysConsumeMouseWheel: false, + }, + scrollBeyondLastLine: false, + wordWrap: 'on', + wrappingIndent: 'indent', + }} + /> +
+
); }; diff --git a/test/functional/apps/saved_objects_management/edit_saved_object.ts b/test/functional/apps/saved_objects_management/edit_saved_object.ts index 1458559d015fe..76877d0319e79 100644 --- a/test/functional/apps/saved_objects_management/edit_saved_object.ts +++ b/test/functional/apps/saved_objects_management/edit_saved_object.ts @@ -15,7 +15,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const testSubjects = getService('testSubjects'); const PageObjects = getPageObjects(['common', 'settings', 'savedObjects']); - const browser = getService('browser'); const find = getService('find'); const focusAndClickButton = async (buttonSubject: string) => { From 238f6da89b42e939d61e61f10da2498e9f09cc04 Mon Sep 17 00:00:00 2001 From: Christiane Heiligers Date: Mon, 13 Sep 2021 15:03:08 -0700 Subject: [PATCH 03/18] Adds link to saved objects api for not found error message --- .../public/doc_links/doc_links_service.ts | 1 + .../not_found_errors.test.tsx.snap | 748 +++++++++++------- .../object_view/components/inspect.tsx | 8 +- .../components/not_found_errors.test.tsx | 23 +- .../components/not_found_errors.tsx | 25 +- .../object_view/saved_object_view.test.tsx | 4 + .../object_view/saved_object_view.tsx | 6 +- .../saved_objects_edition_page.tsx | 2 + 8 files changed, 517 insertions(+), 300 deletions(-) diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 72fa6c5553f77..ec4ffab4cce85 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -241,6 +241,7 @@ export class DocLinksService { kibanaSearchSettings: `${KIBANA_DOCS}advanced-options.html#kibana-search-settings`, visualizationSettings: `${KIBANA_DOCS}advanced-options.html#kibana-visualization-settings`, timelionSettings: `${KIBANA_DOCS}advanced-options.html#kibana-timelion-settings`, + savedObjectsApiList: `${KIBANA_DOCS}saved-objects-api.html#saved-objects-api`, }, ml: { guide: `${ELASTIC_WEBSITE_URL}guide/en/machine-learning/${DOC_LINK_VERSION}/index.html`, diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/not_found_errors.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/not_found_errors.test.tsx.snap index 93b118ef8cccf..c55583679f264 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/not_found_errors.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/not_found_errors.test.tsx.snap @@ -1,353 +1,541 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`NotFoundErrors component renders correctly for index-pattern type 1`] = ` - + } > - - } +
- - + +
+ +
-
- -
-
- - The index pattern associated with this object no longer exists. - -
-
- + + The index pattern associated with this object no longer exists. + +
+
+ + Saved objects APIs + , + } + } + > + If you know what this error means, you can use the + - If you know what this error means, you can use the saved objects api to fix it — otherwise click the delete button above. - -
+ + Saved objects APIs + + + + + + + (opens in a new tab or window) + + + + + + to fix it — otherwise click the delete button above. +
-
-
- -
- - +
+ + + + +
`; exports[`NotFoundErrors component renders correctly for index-pattern-field type 1`] = ` - + } > - - } +
- - + +
+ +
-
- -
-
- - A field associated with this object no longer exists in the index pattern. - -
-
- + + A field associated with this object no longer exists in the index pattern. + +
+
+ + Saved objects APIs + , + } + } + > + If you know what this error means, you can use the + - If you know what this error means, you can use the saved objects api to fix it — otherwise click the delete button above. - -
+ + Saved objects APIs + + + + + + + (opens in a new tab or window) + + + + + + to fix it — otherwise click the delete button above. +
-
-
- -
- - +
+ + + + +
`; exports[`NotFoundErrors component renders correctly for search type 1`] = ` - + } > - - } +
- - + +
+ +
-
- -
-
- - The saved search associated with this object no longer exists. - -
-
- + + The saved search associated with this object no longer exists. + +
+
+ + Saved objects APIs + , + } + } + > + If you know what this error means, you can use the + - If you know what this error means, you can use the saved objects api to fix it — otherwise click the delete button above. - -
+ + Saved objects APIs + + + + + + + (opens in a new tab or window) + + + + + + to fix it — otherwise click the delete button above. +
-
-
- -
- - +
+ + + + +
`; exports[`NotFoundErrors component renders correctly for unknown type 1`] = ` - + } > - - } +
- - + +
+ +
-
- -
-
-
- +
+ + Saved objects APIs + , + } + } + > + If you know what this error means, you can use the + - If you know what this error means, you can use the saved objects api to fix it — otherwise click the delete button above. - -
+ + Saved objects APIs + + + + + + + (opens in a new tab or window) + + + + + + to fix it — otherwise click the delete button above. +
- -
- -
- - +
+ +
+
+
+
`; diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/inspect.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/components/inspect.tsx index 27ebf39642853..0136b1f5e8791 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/components/inspect.tsx +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/inspect.tsx @@ -27,12 +27,6 @@ const codeEditorAriaLabel = (title: string) => const copyToClipboardLabel = i18n.translate('savedObjectsManagement.view.copyToClipboardLabel', { defaultMessage: 'Copy to clipboard', }); -const copyToClipboardAriaLabel = i18n.translate( - 'savedObjectsManagement.view.copyToClipboardAriaLabel', - { - defaultMessage: 'Copy to clipboard', - } -); export const Inspect: FC = ({ object }) => { const title = object.meta.title || 'saved object'; const objectAsJsonString = JSON.stringify(omit(object, 'meta'), null, 2); @@ -43,7 +37,7 @@ export const Inspect: FC = ({ object }) => { {(copy) => ( { const mountError = (type: string) => mount( - + ).find('NotFoundErrors'); it('renders correctly for search type', () => { const mounted = mountError('search'); - expect(mounted).toMatchSnapshot(); + const callOut = mounted.find('EuiCallOut'); + expect(callOut).toMatchSnapshot(); expect(mounted.text()).toMatchInlineSnapshot( - `"There is a problem with this saved objectThe saved search associated with this object no longer exists.If you know what this error means, you can use the saved objects api to fix it — otherwise click the delete button above."` + `"There is a problem with this saved objectThe saved search associated with this object no longer exists.If you know what this error means, you can use the Saved objects APIs(opens in a new tab or window) to fix it — otherwise click the delete button above."` ); }); it('renders correctly for index-pattern type', () => { const mounted = mountError('index-pattern'); - expect(mounted).toMatchSnapshot(); + const callOut = mounted.find('EuiCallOut'); + expect(callOut).toMatchSnapshot(); expect(mounted.text()).toMatchInlineSnapshot( - `"There is a problem with this saved objectThe index pattern associated with this object no longer exists.If you know what this error means, you can use the saved objects api to fix it — otherwise click the delete button above."` + `"There is a problem with this saved objectThe index pattern associated with this object no longer exists.If you know what this error means, you can use the Saved objects APIs(opens in a new tab or window) to fix it — otherwise click the delete button above."` ); }); it('renders correctly for index-pattern-field type', () => { const mounted = mountError('index-pattern-field'); - expect(mounted).toMatchSnapshot(); + const callOut = mounted.find('EuiCallOut'); + expect(callOut).toMatchSnapshot(); expect(mounted.text()).toMatchInlineSnapshot( - `"There is a problem with this saved objectA field associated with this object no longer exists in the index pattern.If you know what this error means, you can use the saved objects api to fix it — otherwise click the delete button above."` + `"There is a problem with this saved objectA field associated with this object no longer exists in the index pattern.If you know what this error means, you can use the Saved objects APIs(opens in a new tab or window) to fix it — otherwise click the delete button above."` ); }); it('renders correctly for unknown type', () => { const mounted = mountError('unknown'); - expect(mounted).toMatchSnapshot(); + const callOut = mounted.find('EuiCallOut'); + expect(callOut).toMatchSnapshot(); expect(mounted.text()).toMatchInlineSnapshot( - `"There is a problem with this saved objectIf you know what this error means, you can use the saved objects api to fix it — otherwise click the delete button above."` + `"There is a problem with this saved objectIf you know what this error means, you can use the Saved objects APIs(opens in a new tab or window) to fix it — otherwise click the delete button above."` ); }); }); diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/not_found_errors.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/components/not_found_errors.tsx index 2a59e9d01093a..e3a349b1f4aa5 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/components/not_found_errors.tsx +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/not_found_errors.tsx @@ -8,13 +8,23 @@ import React from 'react'; import { EuiCallOut } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiLink } from '@elastic/eui'; +import { DocLinksStart } from '../../../../../../core/public'; interface NotFoundErrors { type: string; + docLinks: DocLinksStart['links']; } +const savedObjectsApisLinkText = i18n.translate( + 'savedObjectsManagement.view.howToFixErrorDescriptionLinkText', + { + defaultMessage: 'Saved objects APIs', + } +); -export const NotFoundErrors = ({ type }: NotFoundErrors) => { +export const NotFoundErrors = ({ type, docLinks }: NotFoundErrors) => { const getMessage = () => { switch (type) { case 'search': @@ -58,7 +68,18 @@ export const NotFoundErrors = ({ type }: NotFoundErrors) => {
+ {savedObjectsApisLinkText} + + ), + }} />
diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.test.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.test.tsx index 776358ae25adb..bc3e8fd62515d 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.test.tsx +++ b/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.test.tsx @@ -22,6 +22,7 @@ import { applicationServiceMock, uiSettingsServiceMock, scopedHistoryMock, + docLinksServiceMock, } from '../../../../../core/public/mocks'; import { @@ -39,6 +40,7 @@ describe('SavedObjectEdition', () => { let uiSettings: ReturnType; let history: ReturnType; let applications: ReturnType; + let docLinks: ReturnType; const shallowRender = (overrides: Partial = {}) => { return (shallowWithI18nProvider( @@ -57,6 +59,7 @@ describe('SavedObjectEdition', () => { savedObjects = savedObjectsServiceMock.createStartContract(); uiSettings = uiSettingsServiceMock.createStartContract(); history = scopedHistoryMock.create(); + docLinks = docLinksServiceMock.createStartContract(); applications = applicationServiceMock.createStartContract(); applications.capabilities = { navLinks: {}, @@ -81,6 +84,7 @@ describe('SavedObjectEdition', () => { savedObjectsClient: savedObjects.client, history, uiSettings, + docLinks: docLinks.links, }; bulkGetObjectsMock.mockImplementation(() => [{}]); diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.tsx index eb315a27d7197..d39157f7bb8a3 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.tsx +++ b/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.tsx @@ -19,6 +19,7 @@ import { ScopedHistory, HttpSetup, IUiSettingsClient, + DocLinksStart, } from '../../../../../core/public'; import { Header, Inspect, NotFoundErrors } from './components'; import { bulkGetObjects } from '../../lib/bulk_get_objects'; @@ -35,6 +36,7 @@ export interface SavedObjectEditionProps { savedObjectsClient: SavedObjectsClientContract; history: ScopedHistory; uiSettings: IUiSettingsClient; + docLinks: DocLinksStart['links']; } export interface SavedObjectEditionState { type: string; @@ -91,7 +93,7 @@ export class SavedObjectEdition extends Component< }; render() { - const { capabilities, notFoundType, http, uiSettings } = this.props; + const { capabilities, notFoundType, http, uiSettings, docLinks } = this.props; const { object } = this.state; const { delete: canDelete } = capabilities.savedObjectsManagement as Record; const canView = this.canViewInApp(capabilities, object); @@ -112,7 +114,7 @@ export class SavedObjectEdition extends Component< {notFoundType && ( - + )} {object && ( diff --git a/src/plugins/saved_objects_management/public/management_section/saved_objects_edition_page.tsx b/src/plugins/saved_objects_management/public/management_section/saved_objects_edition_page.tsx index 98c6f0fe8de40..2c154104b1c7e 100644 --- a/src/plugins/saved_objects_management/public/management_section/saved_objects_edition_page.tsx +++ b/src/plugins/saved_objects_management/public/management_section/saved_objects_edition_page.tsx @@ -25,6 +25,7 @@ const SavedObjectsEditionPage = ({ }) => { const { type, id } = useParams<{ type: string; id: string }>(); const capabilities = coreStart.application.capabilities; + const dockLinks = coreStart.docLinks.links; const { search } = useLocation(); const query = parse(search); @@ -59,6 +60,7 @@ const SavedObjectsEditionPage = ({ notFoundType={query.notFound as string} uiSettings={coreStart.uiSettings} history={history} + docLinks={dockLinks} />
); From 9dfde70809846c54a92983f9a166c1cb0a59537c Mon Sep 17 00:00:00 2001 From: Christiane Heiligers Date: Mon, 13 Sep 2021 16:03:09 -0700 Subject: [PATCH 04/18] Update i18n --- .../plugins/translations/translations/ja-JP.json | 14 -------------- .../plugins/translations/translations/zh-CN.json | 14 -------------- 2 files changed, 28 deletions(-) diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index d7c21fd6ee654..35bb65e8dfd32 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -4370,14 +4370,11 @@ "savedObjects.saveModalOrigin.originAfterSavingSwitchLabel": "保存後に{originVerb}から{origin}", "savedObjects.saveModalOrigin.returnToOriginLabel": "戻る", "savedObjects.saveModalOrigin.saveAndReturnLabel": "保存して戻る", - "savedObjectsManagement.breadcrumb.edit": "{savedObjectType}を編集", "savedObjectsManagement.breadcrumb.index": "保存されたオブジェクト", "savedObjectsManagement.deleteConfirm.modalDeleteButtonLabel": "削除", "savedObjectsManagement.deleteConfirm.modalDescription": "このアクションはオブジェクトをKibanaから永久に削除します。", "savedObjectsManagement.deleteConfirm.modalTitle": "「{title}」を削除しますか?", "savedObjectsManagement.deleteSavedObjectsConfirmModalDescription": "この操作は次の保存されたオブジェクトを削除します:", - "savedObjectsManagement.field.offLabel": "オフ", - "savedObjectsManagement.field.onLabel": "オン", "savedObjectsManagement.importSummary.createdCountHeader": "{createdCount}件の新規項目", "savedObjectsManagement.importSummary.createdOutcomeLabel": "作成済み", "savedObjectsManagement.importSummary.errorCountHeader": "{errorCount}件のエラー", @@ -4479,21 +4476,10 @@ "savedObjectsManagement.objectsTable.unableFindSavedObjectNotificationMessage": "保存されたオブジェクトが見つかりません", "savedObjectsManagement.objectsTable.unableFindSavedObjectsNotificationMessage": "保存されたオブジェクトが見つかりません", "savedObjectsManagement.objectView.unableFindSavedObjectNotificationMessage": "保存されたオブジェクトが見つかりません", - "savedObjectsManagement.view.cancelButtonAriaLabel": "キャンセル", - "savedObjectsManagement.view.cancelButtonLabel": "キャンセル", - "savedObjectsManagement.view.deleteItemButtonLabel": "{title}を削除", - "savedObjectsManagement.view.editItemTitle": "{title}の編集", "savedObjectsManagement.view.fieldDoesNotExistErrorMessage": "このオブジェクトに関連付けられたフィールドは、現在このインデックスパターンに存在しません。", - "savedObjectsManagement.view.howToFixErrorDescription": "このエラーの原因がわかる場合は修正してください。わからない場合は上の削除ボタンをクリックしてください。", - "savedObjectsManagement.view.howToModifyObjectDescription": "オブジェクトの編集は上級ユーザー向けです。オブジェクトのプロパティが検証されておらず、無効なオブジェクトはエラー、データ損失、またはそれ以上の問題の原因となります。コードを熟知した人に指示されていない限り、この設定は変更しない方が無難です。", - "savedObjectsManagement.view.howToModifyObjectTitle": "十分ご注意ください!", "savedObjectsManagement.view.indexPatternDoesNotExistErrorMessage": "このオブジェクトに関連付けられたインデックスパターンは現在存在しません。", - "savedObjectsManagement.view.saveButtonAriaLabel": "{ title }オブジェクトを保存", - "savedObjectsManagement.view.saveButtonLabel": "{ title }オブジェクトを保存", "savedObjectsManagement.view.savedObjectProblemErrorMessage": "この保存されたオブジェクトに問題があります", "savedObjectsManagement.view.savedSearchDoesNotExistErrorMessage": "このオブジェクトに関連付けられた保存された検索は現在存在しません。", - "savedObjectsManagement.view.viewItemButtonLabel": "{title}を表示", - "savedObjectsManagement.view.viewItemTitle": "{title}を表示", "security.checkup.dismissButtonText": "閉じる", "security.checkup.dontShowAgain": "今後表示しない", "security.checkup.insecureClusterMessage": "1 ビットを失わないでください。Elastic では無料でデータを保護できます。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index ca0f2359da575..759b0a476a619 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -4410,14 +4410,11 @@ "savedObjects.saveModalOrigin.originAfterSavingSwitchLabel": "保存后{originVerb}至{origin}", "savedObjects.saveModalOrigin.returnToOriginLabel": "返回", "savedObjects.saveModalOrigin.saveAndReturnLabel": "保存并返回", - "savedObjectsManagement.breadcrumb.edit": "编辑 {savedObjectType}", "savedObjectsManagement.breadcrumb.index": "已保存对象", "savedObjectsManagement.deleteConfirm.modalDeleteButtonLabel": "删除", "savedObjectsManagement.deleteConfirm.modalDescription": "此操作会将对象从 Kibana 永久移除。", "savedObjectsManagement.deleteConfirm.modalTitle": "删除“{title}”?", "savedObjectsManagement.deleteSavedObjectsConfirmModalDescription": "此操作将删除以下已保存对象:", - "savedObjectsManagement.field.offLabel": "关闭", - "savedObjectsManagement.field.onLabel": "开启", "savedObjectsManagement.importSummary.createdCountHeader": "{createdCount} 个新", "savedObjectsManagement.importSummary.createdOutcomeLabel": "已创建", "savedObjectsManagement.importSummary.errorCountHeader": "{errorCount} 个错误", @@ -4524,21 +4521,10 @@ "savedObjectsManagement.objectsTable.unableFindSavedObjectNotificationMessage": "找不到已保存对象", "savedObjectsManagement.objectsTable.unableFindSavedObjectsNotificationMessage": "找不到已保存对象", "savedObjectsManagement.objectView.unableFindSavedObjectNotificationMessage": "找不到已保存对象", - "savedObjectsManagement.view.cancelButtonAriaLabel": "取消", - "savedObjectsManagement.view.cancelButtonLabel": "取消", - "savedObjectsManagement.view.deleteItemButtonLabel": "删除“{title}”", - "savedObjectsManagement.view.editItemTitle": "编辑“{title}”", "savedObjectsManagement.view.fieldDoesNotExistErrorMessage": "与此对象关联的字段在该索引模式中已不存在。", - "savedObjectsManagement.view.howToFixErrorDescription": "如果您清楚此错误的含义,请修复该错误 — 否则单击上面的删除按钮。", - "savedObjectsManagement.view.howToModifyObjectDescription": "修改对象仅适用于高级用户。对象属性未得到验证,无效的对象可能会导致错误、数据丢失或更坏的情况发生。除非熟悉该代码的人让您来这里,否则您可能不应到访此处。", - "savedObjectsManagement.view.howToModifyObjectTitle": "谨慎操作!", "savedObjectsManagement.view.indexPatternDoesNotExistErrorMessage": "与此对象关联的索引模式已不存在。", - "savedObjectsManagement.view.saveButtonAriaLabel": "保存 { title } 对象", - "savedObjectsManagement.view.saveButtonLabel": "保存 { title } 对象", "savedObjectsManagement.view.savedObjectProblemErrorMessage": "此已保存对象有问题", "savedObjectsManagement.view.savedSearchDoesNotExistErrorMessage": "与此对象关联的已保存搜索已不存在。", - "savedObjectsManagement.view.viewItemButtonLabel": "查看“{title}”", - "savedObjectsManagement.view.viewItemTitle": "查看“{title}”", "security.checkup.dismissButtonText": "关闭", "security.checkup.dontShowAgain": "不再显示", "security.checkup.insecureClusterMessage": "不要丢失一位。使用 Elastic,免费保护您的数据。", From 8e312052fac319c33c89031dea3569ec49887a5f Mon Sep 17 00:00:00 2001 From: Christiane Heiligers Date: Mon, 13 Sep 2021 17:00:22 -0700 Subject: [PATCH 05/18] Cleans up todo comments --- .../saved_objects_management/public/lib/in_app_url.test.ts | 2 +- src/plugins/saved_objects_management/public/lib/in_app_url.ts | 2 +- .../management_section/object_view/saved_object_view.test.tsx | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/plugins/saved_objects_management/public/lib/in_app_url.test.ts b/src/plugins/saved_objects_management/public/lib/in_app_url.test.ts index b5e5ec9dc4738..28853f836085c 100644 --- a/src/plugins/saved_objects_management/public/lib/in_app_url.test.ts +++ b/src/plugins/saved_objects_management/public/lib/in_app_url.test.ts @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -// TINA TODO: modify to test whatever I replace the contents of `in_app_url` with + import { Capabilities } from '../../../../core/public'; import { canViewInApp } from './in_app_url'; diff --git a/src/plugins/saved_objects_management/public/lib/in_app_url.ts b/src/plugins/saved_objects_management/public/lib/in_app_url.ts index 12b1d4a9e0850..c102ec21a83d0 100644 --- a/src/plugins/saved_objects_management/public/lib/in_app_url.ts +++ b/src/plugins/saved_objects_management/public/lib/in_app_url.ts @@ -7,7 +7,7 @@ */ import { Capabilities } from 'src/core/public'; -// TODO: Modify/change or add something new if we need to to replace this method's use in saved_object_view. + export function canViewInApp(uiCapabilities: Capabilities, type: string): boolean { switch (type) { case 'search': diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.test.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.test.tsx index bc3e8fd62515d..a6ef3132166b8 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.test.tsx +++ b/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.test.tsx @@ -5,9 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -// TODO: Add test using CodeEditor mock. Do a search for how the editor is mocked out elsewhere in the code. -// See https://github.com/elastic/kibana/pull/101392/files as example setup with extra benefit of 'copy-to-clickboard' action. -// also see src/plugins/discover/public/application/components/source_viewer/source_viewer.test.tsx as example for full mount with the editor + import { bulkGetObjectsMock } from './saved_object_view.test.mocks'; import React from 'react'; From 1110ff3df4ccc1e8daec49b4fcc46337e7816cfb Mon Sep 17 00:00:00 2001 From: Christiane Heiligers Date: Tue, 14 Sep 2021 09:42:33 -0700 Subject: [PATCH 06/18] Updates test for ensuring we can inspect a saved object in a non-default space --- .../apps/saved_objects_management/spaces_integration.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/test/functional/apps/saved_objects_management/spaces_integration.ts b/x-pack/test/functional/apps/saved_objects_management/spaces_integration.ts index 28d04c1f9c54c..9394a05c6e336 100644 --- a/x-pack/test/functional/apps/saved_objects_management/spaces_integration.ts +++ b/x-pack/test/functional/apps/saved_objects_management/spaces_integration.ts @@ -14,7 +14,6 @@ const getSpacePrefix = (spaceId: string) => { export default function ({ getPageObjects, getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); - const testSubjects = getService('testSubjects'); const PageObjects = getPageObjects([ 'common', 'security', @@ -22,6 +21,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { 'spaceSelector', 'settings', ]); + const find = getService('find'); const spaceId = 'space_1'; @@ -54,9 +54,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.waitUntilUrlIncludes(getSpacePrefix(spaceId)); - expect(await testSubjects.getAttribute(`savedObjects-editField-title`, 'value')).to.eql( - 'A Pie' - ); + const inspectContainer = await find.byClassName('kibanaCodeEditor'); + const visibleContainerText = await inspectContainer.getVisibleText(); + expect(visibleContainerText.includes('A Pie')); }); }); } From 8c923173d37711cbe40e87e083dd3672cfd4704b Mon Sep 17 00:00:00 2001 From: Christiane Heiligers Date: Tue, 14 Sep 2021 13:19:23 -0700 Subject: [PATCH 07/18] Updates unit test snapshot and refactors security-related saved objects inspect functional tests --- .../saved_object_view.test.tsx.snap | 1 - .../object_view/saved_object_view.tsx | 2 +- .../saved_objects_management_security.ts | 41 ++++++++++++++----- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/__snapshots__/saved_object_view.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/object_view/__snapshots__/saved_object_view.test.tsx.snap index 54775a2d2ac36..bcf128431bd5e 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/__snapshots__/saved_object_view.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/object_view/__snapshots__/saved_object_view.test.tsx.snap @@ -43,7 +43,6 @@ exports[`SavedObjectEdition should render normally 1`] = ` grow={true} > - + )} diff --git a/x-pack/test/functional/apps/saved_objects_management/feature_controls/saved_objects_management_security.ts b/x-pack/test/functional/apps/saved_objects_management/feature_controls/saved_objects_management_security.ts index c25c81b9a38b3..98c2dfc344177 100644 --- a/x-pack/test/functional/apps/saved_objects_management/feature_controls/saved_objects_management_security.ts +++ b/x-pack/test/functional/apps/saved_objects_management/feature_controls/saved_objects_management_security.ts @@ -14,6 +14,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const PageObjects = getPageObjects(['common', 'settings', 'security', 'error', 'savedObjects']); let version: string = ''; + const find = getService('find'); describe('feature controls saved objects management', () => { before(async () => { @@ -109,12 +110,14 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { expect(actual).to.be(true); }); }); - - describe('edit visualization', () => { + // From https://github.com/elastic/kibana/issues/59588 edit view became read-only json view + // test description changed from "edit" to "inspect" + // Skipping the test to allow code owners to delete or modify the test. + describe('inspect visualization', () => { before(async () => { await PageObjects.common.navigateToUrl( 'management', - 'kibana/objects/savedVisualizations/75c3e060-1e7c-11e9-8488-65449e65d0ed', + 'kibana/objects/visualization/75c3e060-1e7c-11e9-8488-65449e65d0ed', { shouldLoginIfPrompted: false, shouldUseHashForSubUrl: false, @@ -126,11 +129,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await testSubjects.existOrFail('savedObjectEditDelete'); }); - it('shows save button', async () => { + // no longer a feature + it.skip('shows save button', async () => { await testSubjects.existOrFail('savedObjectEditSave'); }); - it('has inputs without readonly attributes', async () => { + // no longer a feature + it.skip('has inputs without readonly attributes', async () => { const form = await testSubjects.find('savedObjectEditForm'); const inputs = await form.findAllByCssSelector('input'); expect(inputs.length).to.be.greaterThan(0); @@ -224,17 +229,30 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); }); - describe('edit visualization', () => { + // From https://github.com/elastic/kibana/issues/59588 edit view became read-only json view + // test description changed from "edit" to "inspect" + // Skipping the test to allow code owners to delete or modify the test. + describe('inspect visualization', () => { before(async () => { + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaSavedObjects(); + const objects = await PageObjects.savedObjects.getRowTitles(); + expect(objects.includes('A Pie')).to.be(true); + await PageObjects.savedObjects.clickInspectByTitle('A Pie'); await PageObjects.common.navigateToUrl( 'management', - 'kibana/objects/savedVisualizations/75c3e060-1e7c-11e9-8488-65449e65d0ed', + 'kibana/objects/visualization/75c3e060-1e7c-11e9-8488-65449e65d0ed', { shouldLoginIfPrompted: false, shouldUseHashForSubUrl: false, } ); - await testSubjects.existOrFail('savedObjectsEdit'); + }); + + it('allows viewing the object', async () => { + const inspectContainer = await find.byClassName('kibanaCodeEditor'); + const visibleContainerText = await inspectContainer.getVisibleText(); + expect(visibleContainerText.includes('A Pie')); }); it('does not show delete button', async () => { @@ -245,7 +263,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await testSubjects.missingOrFail('savedObjectEditSave'); }); - it('has inputs with only readonly attributes', async () => { + // No longer a feature + it.skip('has inputs with only readonly attributes', async () => { const form = await testSubjects.find('savedObjectEditForm'); const inputs = await form.findAllByCssSelector('input'); expect(inputs.length).to.be.greaterThan(0); @@ -310,11 +329,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); }); - describe('edit visualization', () => { + describe('inspect visualization', () => { it('redirects to management home', async () => { await PageObjects.common.navigateToUrl( 'management', - 'kibana/objects/savedVisualizations/75c3e060-1e7c-11e9-8488-65449e65d0ed', + 'kibana/objects/visualization/75c3e060-1e7c-11e9-8488-65449e65d0ed', { shouldLoginIfPrompted: false, ensureCurrentUrl: false, From b31a0a8ce4febf603dcf6dfc4295755b4682211b Mon Sep 17 00:00:00 2001 From: Christiane Heiligers Date: Thu, 16 Sep 2021 11:38:37 -0700 Subject: [PATCH 08/18] removes getEditUrl from saved object loader searchSavedObjectType, createDashboardSavedObjectType and visualizationSavedObjectType. Adds redirect to valid editUrl on Inspect if one is defined --- .../dashboard/server/saved_objects/dashboard.ts | 3 --- .../discover/server/saved_objects/search.ts | 3 --- .../objects_table/saved_objects_table.test.tsx | 3 --- .../saved_objects_table_page.tsx | 15 ++++++++++----- .../server/saved_objects/visualization.ts | 3 --- 5 files changed, 10 insertions(+), 17 deletions(-) diff --git a/src/plugins/dashboard/server/saved_objects/dashboard.ts b/src/plugins/dashboard/server/saved_objects/dashboard.ts index dfda251e28779..068883c429e61 100644 --- a/src/plugins/dashboard/server/saved_objects/dashboard.ts +++ b/src/plugins/dashboard/server/saved_objects/dashboard.ts @@ -27,9 +27,6 @@ export const createDashboardSavedObjectType = ({ getTitle(obj) { return obj.attributes.title; }, - getEditUrl(obj) { - return `/management/kibana/objects/savedDashboards/${encodeURIComponent(obj.id)}`; - }, getInAppUrl(obj) { return { path: `/app/dashboards#/view/${encodeURIComponent(obj.id)}`, diff --git a/src/plugins/discover/server/saved_objects/search.ts b/src/plugins/discover/server/saved_objects/search.ts index 070f0253f17e0..46284f3cf33b6 100644 --- a/src/plugins/discover/server/saved_objects/search.ts +++ b/src/plugins/discover/server/saved_objects/search.ts @@ -20,9 +20,6 @@ export const searchSavedObjectType: SavedObjectsType = { getTitle(obj) { return obj.attributes.title; }, - getEditUrl(obj) { - return `/management/kibana/objects/savedSearches/${encodeURIComponent(obj.id)}`; - }, getInAppUrl(obj) { return { path: `/app/discover#/view/${encodeURIComponent(obj.id)}`, diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx index 9b8474fc08bbd..1edae6fd53919 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx @@ -159,7 +159,6 @@ describe('SavedObjectsTable', () => { meta: { title: `MySearch`, icon: 'search', - editUrl: '/management/kibana/objects/savedSearches/2', inAppUrl: { path: '/discover/2', uiCapabilitiesPath: 'discover.show', @@ -172,7 +171,6 @@ describe('SavedObjectsTable', () => { meta: { title: `MyDashboard`, icon: 'dashboardApp', - editUrl: '/management/kibana/objects/savedDashboards/3', inAppUrl: { path: '/dashboard/3', uiCapabilitiesPath: 'dashboard.show', @@ -185,7 +183,6 @@ describe('SavedObjectsTable', () => { meta: { title: `MyViz`, icon: 'visualizeApp', - editUrl: '/management/kibana/objects/savedVisualizations/4', inAppUrl: { path: '/edit/4', uiCapabilitiesPath: 'visualize.show', diff --git a/src/plugins/saved_objects_management/public/management_section/saved_objects_table_page.tsx b/src/plugins/saved_objects_management/public/management_section/saved_objects_table_page.tsx index b6ca2a9da3489..b760d2ea5eeca 100644 --- a/src/plugins/saved_objects_management/public/management_section/saved_objects_table_page.tsx +++ b/src/plugins/saved_objects_management/public/management_section/saved_objects_table_page.tsx @@ -94,11 +94,16 @@ const SavedObjectsTablePage = ({ applications={coreStart.application} perPageConfig={itemsPerPage} goInspectObject={(savedObject) => { - coreStart.application.navigateToUrl( - coreStart.http.basePath.prepend( - `/app/management/kibana/objects/${savedObject.type}/${savedObject.id}` - ) - ); + const { editUrl } = savedObject.meta; + if (editUrl) { + coreStart.application.navigateToUrl(coreStart.http.basePath.prepend(`/app${editUrl}`)); + } else { + coreStart.application.navigateToUrl( + coreStart.http.basePath.prepend( + `/app/management/kibana/objects/${savedObject.type}/${savedObject.id}` + ) + ); + } }} canGoInApp={(savedObject) => { const { inAppUrl } = savedObject.meta; diff --git a/src/plugins/visualizations/server/saved_objects/visualization.ts b/src/plugins/visualizations/server/saved_objects/visualization.ts index 880e277294fc3..53027d5d5046c 100644 --- a/src/plugins/visualizations/server/saved_objects/visualization.ts +++ b/src/plugins/visualizations/server/saved_objects/visualization.ts @@ -20,9 +20,6 @@ export const visualizationSavedObjectType: SavedObjectsType = { getTitle(obj) { return obj.attributes.title; }, - getEditUrl(obj) { - return `/management/kibana/objects/savedVisualizations/${encodeURIComponent(obj.id)}`; - }, getInAppUrl(obj) { return { path: `/app/visualize#/edit/${encodeURIComponent(obj.id)}`, From 3c89d0b11c16b8144bbb75a5df8cf4f0ddb76a67 Mon Sep 17 00:00:00 2001 From: Christiane Heiligers Date: Thu, 16 Sep 2021 15:40:49 -0700 Subject: [PATCH 09/18] Addresses draft comments --- .../__snapshots__/header.test.tsx.snap | 26 +++++++++++---- .../object_view/components/header.test.tsx | 32 ++++--------------- .../object_view/components/header.tsx | 11 ++++--- .../object_view/components/inspect.tsx | 1 - .../object_view/saved_object_view.test.tsx | 19 ++++++----- .../object_view/saved_object_view.tsx | 5 +-- .../apps/saved_objects_management/index.ts | 2 +- ...ved_object.ts => inspect_saved_objects.ts} | 4 +++ 8 files changed, 49 insertions(+), 51 deletions(-) rename test/functional/apps/saved_objects_management/{edit_saved_object.ts => inspect_saved_objects.ts} (94%) diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/header.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/header.test.tsx.snap index 662b183444818..26ff23b50d87b 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/header.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/header.test.tsx.snap @@ -21,9 +21,13 @@ exports[`Intro component renders correctly 1`] = ` size="s" > , , - View in application + View saved object diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/header.test.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/components/header.test.tsx index 4a5c859fabf1d..796632f9747a9 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/components/header.test.tsx +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/header.test.tsx @@ -19,6 +19,7 @@ describe('Intro component', () => { type: string; viewUrl: string; onDeleteClick: () => void; + title?: string; }) => mount( @@ -42,32 +43,11 @@ describe('Intro component', () => { expect(mounted).toMatchSnapshot(); }); - it('displays correct title depending on canEdit', () => { - let mounted = mountHeader({ - ...defaultProps, - canEdit: true, - }); - expect(mounted.find('h1').text()).toMatchInlineSnapshot(`"Inspect saved object"`); - - mounted = mountHeader({ - ...defaultProps, - canEdit: false, - }); - expect(mounted.find('h1').text()).toMatchInlineSnapshot(`"Inspect saved object"`); - }); - - it('displays correct title depending on type', () => { - let mounted = mountHeader({ - ...defaultProps, - type: 'some-type', - }); - expect(mounted.find('h1').text()).toMatchInlineSnapshot(`"Inspect saved object"`); - - mounted = mountHeader({ - ...defaultProps, - type: 'another-type', - }); - expect(mounted.find('h1').text()).toMatchInlineSnapshot(`"Inspect saved object"`); + it('displays correct title if one is provided', () => { + let mounted = mountHeader({ ...defaultProps, title: 'my saved search' }); + expect(mounted.find('h1').text()).toMatchInlineSnapshot(`"Inspect my saved search"`); + mounted = mountHeader({ ...defaultProps, title: 'my other saved search' }); + expect(mounted.find('h1').text()).toMatchInlineSnapshot(`"Inspect my other saved search"`); }); it('only displays delete button if canDelete is true', () => { diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/header.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/components/header.tsx index f6d8a33eb1a34..10374b839ca48 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/components/header.tsx +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/header.tsx @@ -16,14 +16,16 @@ interface HeaderProps { canViewInApp: boolean; viewUrl: string; onDeleteClick: () => void; + title?: string; } -export const Header = ({ canDelete, canViewInApp, viewUrl, onDeleteClick }: HeaderProps) => { +export const Header = ({ canDelete, canViewInApp, viewUrl, onDeleteClick, title }: HeaderProps) => { return ( ), diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/inspect.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/components/inspect.tsx index 0136b1f5e8791..1128ba3139762 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/components/inspect.tsx +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/inspect.tsx @@ -52,7 +52,6 @@ export const Inspect: FC = ({ object }) => { {}} aria-label={codeEditorAriaLabel(title)} height={'100%'} options={{ diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.test.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.test.tsx index a6ef3132166b8..92792e479daca 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.test.tsx +++ b/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.test.tsx @@ -74,7 +74,7 @@ describe('SavedObjectEdition', () => { defaultProps = { id: '1', - savedObjectType: 'index-pattern', + savedObjectType: 'dashboard', http, capabilities: applications.capabilities, overlays, @@ -93,17 +93,16 @@ describe('SavedObjectEdition', () => { Promise.resolve([ { id: '1', - type: 'index-pattern', + type: 'dashboard', attributes: { - title: `MyIndexPattern*`, + title: `MyDashboard*`, }, meta: { - title: `MyIndexPattern*`, - icon: 'indexPatternApp', - editUrl: '#/management/kibana/indexPatterns/patterns/1', + title: `MyDashboard*`, + icon: 'dashboardApp', inAppUrl: { - path: '/management/kibana/indexPatterns/patterns/1', - uiCapabilitiesPath: 'management.kibana.indexPatterns', + path: '/app/dashboards#/view/1', + uiCapabilitiesPath: 'management.kibana.dashboard', }, }, }, @@ -131,7 +130,7 @@ describe('SavedObjectEdition', () => { await new Promise((resolve) => process.nextTick(resolve)); component.update(); - expect(notifications.toasts.addDanger).toHaveBeenCalled(); + expect(notifications.toasts.addDanger).toHaveBeenCalledTimes(1); }); it('should add danger toast when bulk get throws', async () => { @@ -140,7 +139,7 @@ describe('SavedObjectEdition', () => { await new Promise((resolve) => process.nextTick(resolve)); component.update(); - expect(notifications.toasts.addDanger).toHaveBeenCalled(); + expect(notifications.toasts.addDanger).toHaveBeenCalledTimes(1); }); it('should pass the correct props to the child components', async () => { diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.tsx index fffae8856cd0e..8e5c5a0cb5ca5 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.tsx +++ b/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.tsx @@ -85,12 +85,12 @@ export class SavedObjectEdition extends Component< }); } - canViewInApp = (capabilities: Capabilities, obj?: SavedObjectWithMetadata) => { + canViewInApp(capabilities: Capabilities, obj?: SavedObjectWithMetadata) { return obj && obj.meta.inAppUrl ? get(capabilities, obj?.meta.inAppUrl?.uiCapabilitiesPath, false) && Boolean(obj?.meta.inAppUrl?.path) : false; - }; + } render() { const { capabilities, notFoundType, http, uiSettings, docLinks } = this.props; @@ -110,6 +110,7 @@ export class SavedObjectEdition extends Component< canViewInApp={canView} onDeleteClick={() => this.delete()} viewUrl={http.basePath.prepend(object?.meta.inAppUrl?.path || '')} + title={object?.meta.title} /> {notFoundType && ( diff --git a/test/functional/apps/saved_objects_management/index.ts b/test/functional/apps/saved_objects_management/index.ts index 0b367b284e741..12e0cc8863f12 100644 --- a/test/functional/apps/saved_objects_management/index.ts +++ b/test/functional/apps/saved_objects_management/index.ts @@ -11,7 +11,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function savedObjectsManagementApp({ loadTestFile }: FtrProviderContext) { describe('saved objects management', function savedObjectsManagementAppTestSuite() { this.tags('ciGroup7'); - loadTestFile(require.resolve('./edit_saved_object')); + loadTestFile(require.resolve('./inspect_saved_objects')); loadTestFile(require.resolve('./show_relationships')); }); } diff --git a/test/functional/apps/saved_objects_management/edit_saved_object.ts b/test/functional/apps/saved_objects_management/inspect_saved_objects.ts similarity index 94% rename from test/functional/apps/saved_objects_management/edit_saved_object.ts rename to test/functional/apps/saved_objects_management/inspect_saved_objects.ts index 76877d0319e79..55d325dc690e2 100644 --- a/test/functional/apps/saved_objects_management/edit_saved_object.ts +++ b/test/functional/apps/saved_objects_management/inspect_saved_objects.ts @@ -29,6 +29,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }; describe('saved objects edition page', () => { + debugger; beforeEach(async () => { await esArchiver.load( 'test/functional/fixtures/es_archiver/saved_objects_management/edit_saved_object' @@ -51,8 +52,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); const inspectContainer = await find.byClassName('kibanaCodeEditor'); const visibleContainerText = await inspectContainer.getVisibleText(); + debugger; // ensure that something renders visibly expect(visibleContainerText.includes('A Dashboard')); + const textAsJson = JSON.parse(visibleContainerText); + expect(Object.keys(textAsJson)).to.eql(['id', 'attributes', 'references', 'type']); }); it('allows to delete a saved object', async () => { From 3f2b30d000cff529b4094f0e40e39277a578700f Mon Sep 17 00:00:00 2001 From: Christiane Heiligers Date: Thu, 16 Sep 2021 16:57:21 -0700 Subject: [PATCH 10/18] improves functional tests --- .../inspect_saved_objects.ts | 19 ++++++++++++++----- .../spaces_integration.ts | 15 +++++++++++++++ 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/test/functional/apps/saved_objects_management/inspect_saved_objects.ts b/test/functional/apps/saved_objects_management/inspect_saved_objects.ts index 55d325dc690e2..839c262acffa0 100644 --- a/test/functional/apps/saved_objects_management/inspect_saved_objects.ts +++ b/test/functional/apps/saved_objects_management/inspect_saved_objects.ts @@ -27,9 +27,12 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { // Allow some time for the transition/animations to occur before assuming the click is done await delay(10); }; + const textIncludesAll = (text: string, items: string[]) => { + const bools = items.map((item) => !!text.includes(item)); + return bools.every((currBool) => currBool === true); + }; describe('saved objects edition page', () => { - debugger; beforeEach(async () => { await esArchiver.load( 'test/functional/fixtures/es_archiver/saved_objects_management/edit_saved_object' @@ -52,11 +55,17 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); const inspectContainer = await find.byClassName('kibanaCodeEditor'); const visibleContainerText = await inspectContainer.getVisibleText(); - debugger; // ensure that something renders visibly - expect(visibleContainerText.includes('A Dashboard')); - const textAsJson = JSON.parse(visibleContainerText); - expect(Object.keys(textAsJson)).to.eql(['id', 'attributes', 'references', 'type']); + expect( + textIncludesAll(visibleContainerText, [ + 'A Dashboard', + 'title', + 'id', + 'type', + 'attributes', + 'references', + ]) + ).to.be(true); }); it('allows to delete a saved object', async () => { diff --git a/x-pack/test/functional/apps/saved_objects_management/spaces_integration.ts b/x-pack/test/functional/apps/saved_objects_management/spaces_integration.ts index 9394a05c6e336..e4da0b341dce9 100644 --- a/x-pack/test/functional/apps/saved_objects_management/spaces_integration.ts +++ b/x-pack/test/functional/apps/saved_objects_management/spaces_integration.ts @@ -25,6 +25,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const spaceId = 'space_1'; + const textIncludesAll = (text: string, items: string[]) => { + const bools = items.map((item) => !!text.includes(item)); + return bools.every((currBool) => currBool === true); + }; + describe('spaces integration', () => { before(async () => { await esArchiver.load( @@ -56,6 +61,16 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const inspectContainer = await find.byClassName('kibanaCodeEditor'); const visibleContainerText = await inspectContainer.getVisibleText(); + expect( + textIncludesAll(visibleContainerText, [ + 'A Pie', + 'title', + 'id', + 'type', + 'attributes', + 'references', + ]) + ).to.be(true); expect(visibleContainerText.includes('A Pie')); }); }); From dac2f76552aa04a846ecf11cb32ceeac89548dc9 Mon Sep 17 00:00:00 2001 From: Christiane Heiligers Date: Thu, 16 Sep 2021 17:14:24 -0700 Subject: [PATCH 11/18] Fixes som api_integration test --- test/api_integration/apis/saved_objects_management/find.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/test/api_integration/apis/saved_objects_management/find.ts b/test/api_integration/apis/saved_objects_management/find.ts index 6e36303cc1fe0..4d8fd47c180a8 100644 --- a/test/api_integration/apis/saved_objects_management/find.ts +++ b/test/api_integration/apis/saved_objects_management/find.ts @@ -180,8 +180,6 @@ export default function ({ getService }: FtrProviderContext) { icon: 'discoverApp', title: 'OneRecord', hiddenType: false, - editUrl: - '/management/kibana/objects/savedSearches/960372e0-3224-11e8-a572-ffca06da1357', inAppUrl: { path: '/app/discover#/view/960372e0-3224-11e8-a572-ffca06da1357', uiCapabilitiesPath: 'discover.show', @@ -200,8 +198,6 @@ export default function ({ getService }: FtrProviderContext) { icon: 'dashboardApp', title: 'Dashboard', hiddenType: false, - editUrl: - '/management/kibana/objects/savedDashboards/b70c7ae0-3224-11e8-a572-ffca06da1357', inAppUrl: { path: '/app/dashboards#/view/b70c7ae0-3224-11e8-a572-ffca06da1357', uiCapabilitiesPath: 'dashboard.show', @@ -220,8 +216,6 @@ export default function ({ getService }: FtrProviderContext) { icon: 'visualizeApp', title: 'VisualizationFromSavedSearch', hiddenType: false, - editUrl: - '/management/kibana/objects/savedVisualizations/a42c0580-3224-11e8-a572-ffca06da1357', inAppUrl: { path: '/app/visualize#/edit/a42c0580-3224-11e8-a572-ffca06da1357', uiCapabilitiesPath: 'visualize.show', From 8df5007a35c6a09e60af6c31aea7b6efe8ef35c4 Mon Sep 17 00:00:00 2001 From: Christiane Heiligers Date: Fri, 17 Sep 2021 15:37:32 -0700 Subject: [PATCH 12/18] Updates unit test snapshots, sets available in relationships flyout actions as true based on object type and id being present --- .../saved_object_view.test.tsx.snap | 16 ++++++++-------- .../__snapshots__/inspect.test.tsx.snap | 1 - .../saved_objects_table.test.tsx.snap | 3 --- .../objects_table/components/relationships.tsx | 2 +- .../apis/saved_objects_management/find.ts | 2 -- 5 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/__snapshots__/saved_object_view.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/object_view/__snapshots__/saved_object_view.test.tsx.snap index bcf128431bd5e..23df617b7eebd 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/__snapshots__/saved_object_view.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/object_view/__snapshots__/saved_object_view.test.tsx.snap @@ -36,7 +36,8 @@ exports[`SavedObjectEdition should render normally 1`] = ` canDelete={false} canViewInApp={true} onDeleteClick={[Function]} - viewUrl="/management/kibana/indexPatterns/patterns/1" + title="MyDashboard*" + viewUrl="/app/dashboards#/view/1" /> diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/inspect.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/inspect.test.tsx.snap index a49c70ec358d2..a825c2eca6850 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/inspect.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/inspect.test.tsx.snap @@ -5,7 +5,6 @@ exports[`Inspect component renders correctly 1`] = ` aria-label="inspect MyIndexPattern* object" height="100%" languageId="xjson" - onChange={[Function]} options={ Object { "automaticLayout": false, diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap index 46ea319ebc168..327a9635462cc 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap @@ -192,7 +192,6 @@ exports[`SavedObjectsTable should render normally 1`] = ` Object { "id": "2", "meta": Object { - "editUrl": "/management/kibana/objects/savedSearches/2", "icon": "search", "inAppUrl": Object { "path": "/discover/2", @@ -205,7 +204,6 @@ exports[`SavedObjectsTable should render normally 1`] = ` Object { "id": "3", "meta": Object { - "editUrl": "/management/kibana/objects/savedDashboards/3", "icon": "dashboardApp", "inAppUrl": Object { "path": "/dashboard/3", @@ -218,7 +216,6 @@ exports[`SavedObjectsTable should render normally 1`] = ` Object { "id": "4", "meta": Object { - "editUrl": "/management/kibana/objects/savedVisualizations/4", "icon": "visualizeApp", "inAppUrl": Object { "path": "/edit/4", diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/relationships.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/relationships.tsx index f9171c7928dbe..8eb48ac91da66 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/relationships.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/relationships.tsx @@ -298,7 +298,7 @@ export class Relationships extends Component goInspectObject(object), - available: (object: SavedObjectWithMetadata) => !!object.meta.editUrl, + available: (object: SavedObjectWithMetadata) => !!(object.type && object.id), }, ], }, diff --git a/test/api_integration/apis/saved_objects_management/find.ts b/test/api_integration/apis/saved_objects_management/find.ts index 4d8fd47c180a8..a7c4110636c38 100644 --- a/test/api_integration/apis/saved_objects_management/find.ts +++ b/test/api_integration/apis/saved_objects_management/find.ts @@ -226,8 +226,6 @@ export default function ({ getService }: FtrProviderContext) { icon: 'visualizeApp', title: 'Visualization', hiddenType: false, - editUrl: - '/management/kibana/objects/savedVisualizations/add810b0-3224-11e8-a572-ffca06da1357', inAppUrl: { path: '/app/visualize#/edit/add810b0-3224-11e8-a572-ffca06da1357', uiCapabilitiesPath: 'visualize.show', From 9de3eb1fbb0d2370a7d6118e3d9cb028345dc937 Mon Sep 17 00:00:00 2001 From: Christiane Heiligers Date: Fri, 17 Sep 2021 16:23:41 -0700 Subject: [PATCH 13/18] Updates api_integration test making editUrl optional in the schema --- .../saved_objects_management/relationships.ts | 28 +------------------ 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/test/api_integration/apis/saved_objects_management/relationships.ts b/test/api_integration/apis/saved_objects_management/relationships.ts index aa488942edbeb..124a9f6ac34f4 100644 --- a/test/api_integration/apis/saved_objects_management/relationships.ts +++ b/test/api_integration/apis/saved_objects_management/relationships.ts @@ -21,7 +21,7 @@ export default function ({ getService }: FtrProviderContext) { meta: schema.object({ title: schema.string(), icon: schema.string(), - editUrl: schema.string(), + editUrl: schema.maybe(schema.string()), inAppUrl: schema.object({ path: schema.string(), uiCapabilitiesPath: schema.string(), @@ -104,8 +104,6 @@ export default function ({ getService }: FtrProviderContext) { meta: { title: 'VisualizationFromSavedSearch', icon: 'visualizeApp', - editUrl: - '/management/kibana/objects/savedVisualizations/a42c0580-3224-11e8-a572-ffca06da1357', inAppUrl: { path: '/app/visualize#/edit/a42c0580-3224-11e8-a572-ffca06da1357', uiCapabilitiesPath: 'visualize.show', @@ -149,8 +147,6 @@ export default function ({ getService }: FtrProviderContext) { meta: { icon: 'visualizeApp', title: 'VisualizationFromSavedSearch', - editUrl: - '/management/kibana/objects/savedVisualizations/a42c0580-3224-11e8-a572-ffca06da1357', inAppUrl: { path: '/app/visualize#/edit/a42c0580-3224-11e8-a572-ffca06da1357', uiCapabilitiesPath: 'visualize.show', @@ -194,8 +190,6 @@ export default function ({ getService }: FtrProviderContext) { meta: { icon: 'visualizeApp', title: 'Visualization', - editUrl: - '/management/kibana/objects/savedVisualizations/add810b0-3224-11e8-a572-ffca06da1357', inAppUrl: { path: '/app/visualize#/edit/add810b0-3224-11e8-a572-ffca06da1357', uiCapabilitiesPath: 'visualize.show', @@ -211,8 +205,6 @@ export default function ({ getService }: FtrProviderContext) { meta: { icon: 'visualizeApp', title: 'VisualizationFromSavedSearch', - editUrl: - '/management/kibana/objects/savedVisualizations/a42c0580-3224-11e8-a572-ffca06da1357', inAppUrl: { path: '/app/visualize#/edit/a42c0580-3224-11e8-a572-ffca06da1357', uiCapabilitiesPath: 'visualize.show', @@ -236,8 +228,6 @@ export default function ({ getService }: FtrProviderContext) { meta: { icon: 'visualizeApp', title: 'Visualization', - editUrl: - '/management/kibana/objects/savedVisualizations/add810b0-3224-11e8-a572-ffca06da1357', inAppUrl: { path: '/app/visualize#/edit/add810b0-3224-11e8-a572-ffca06da1357', uiCapabilitiesPath: 'visualize.show', @@ -253,8 +243,6 @@ export default function ({ getService }: FtrProviderContext) { meta: { icon: 'visualizeApp', title: 'VisualizationFromSavedSearch', - editUrl: - '/management/kibana/objects/savedVisualizations/a42c0580-3224-11e8-a572-ffca06da1357', inAppUrl: { path: '/app/visualize#/edit/a42c0580-3224-11e8-a572-ffca06da1357', uiCapabilitiesPath: 'visualize.show', @@ -298,8 +286,6 @@ export default function ({ getService }: FtrProviderContext) { meta: { icon: 'discoverApp', title: 'OneRecord', - editUrl: - '/management/kibana/objects/savedSearches/960372e0-3224-11e8-a572-ffca06da1357', inAppUrl: { path: '/app/discover#/view/960372e0-3224-11e8-a572-ffca06da1357', uiCapabilitiesPath: 'discover.show', @@ -315,8 +301,6 @@ export default function ({ getService }: FtrProviderContext) { meta: { icon: 'dashboardApp', title: 'Dashboard', - editUrl: - '/management/kibana/objects/savedDashboards/b70c7ae0-3224-11e8-a572-ffca06da1357', inAppUrl: { path: '/app/dashboards#/view/b70c7ae0-3224-11e8-a572-ffca06da1357', uiCapabilitiesPath: 'dashboard.show', @@ -342,8 +326,6 @@ export default function ({ getService }: FtrProviderContext) { meta: { icon: 'discoverApp', title: 'OneRecord', - editUrl: - '/management/kibana/objects/savedSearches/960372e0-3224-11e8-a572-ffca06da1357', inAppUrl: { path: '/app/discover#/view/960372e0-3224-11e8-a572-ffca06da1357', uiCapabilitiesPath: 'discover.show', @@ -387,8 +369,6 @@ export default function ({ getService }: FtrProviderContext) { meta: { icon: 'discoverApp', title: 'OneRecord', - editUrl: - '/management/kibana/objects/savedSearches/960372e0-3224-11e8-a572-ffca06da1357', inAppUrl: { path: '/app/discover#/view/960372e0-3224-11e8-a572-ffca06da1357', uiCapabilitiesPath: 'discover.show', @@ -404,8 +384,6 @@ export default function ({ getService }: FtrProviderContext) { meta: { icon: 'visualizeApp', title: 'Visualization', - editUrl: - '/management/kibana/objects/savedVisualizations/add810b0-3224-11e8-a572-ffca06da1357', inAppUrl: { path: '/app/visualize#/edit/add810b0-3224-11e8-a572-ffca06da1357', uiCapabilitiesPath: 'visualize.show', @@ -431,8 +409,6 @@ export default function ({ getService }: FtrProviderContext) { meta: { icon: 'discoverApp', title: 'OneRecord', - editUrl: - '/management/kibana/objects/savedSearches/960372e0-3224-11e8-a572-ffca06da1357', inAppUrl: { path: '/app/discover#/view/960372e0-3224-11e8-a572-ffca06da1357', uiCapabilitiesPath: 'discover.show', @@ -477,8 +453,6 @@ export default function ({ getService }: FtrProviderContext) { { id: 'add810b0-3224-11e8-a572-ffca06da1357', meta: { - editUrl: - '/management/kibana/objects/savedVisualizations/add810b0-3224-11e8-a572-ffca06da1357', icon: 'visualizeApp', inAppUrl: { path: '/app/visualize#/edit/add810b0-3224-11e8-a572-ffca06da1357', From 7b748d069e1e564261e7c1ba596d890d59a217ca Mon Sep 17 00:00:00 2001 From: Christiane Heiligers Date: Mon, 20 Sep 2021 12:00:31 -0700 Subject: [PATCH 14/18] Removes serviceRegistry from the top most UI layer --- .../objects_table/components/flyout.test.tsx | 2 -- .../management_section/objects_table/components/flyout.tsx | 2 -- .../objects_table/saved_objects_table.test.tsx | 2 -- .../management_section/objects_table/saved_objects_table.tsx | 3 --- .../public/management_section/saved_objects_table_page.tsx | 1 - 5 files changed, 10 deletions(-) diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.test.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.test.tsx index 30d172b89256e..acdab1db40370 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.test.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.test.tsx @@ -11,7 +11,6 @@ import { importFileMock, resolveImportErrorsMock } from './flyout.test.mocks'; import React from 'react'; import { shallowWithI18nProvider } from '@kbn/test/jest'; import { coreMock, httpServiceMock } from '../../../../../../core/public/mocks'; -import { serviceRegistryMock } from '../../../services/service_registry.mock'; import { Flyout, FlyoutProps, FlyoutState } from './flyout'; import { ShallowWrapper } from 'enzyme'; import { dataPluginMock } from '../../../../../data/public/mocks'; @@ -49,7 +48,6 @@ describe('Flyout', () => { } as any, http, allowedTypes: ['search', 'index-pattern', 'visualization'], - serviceRegistry: serviceRegistryMock.create(), search, basePath, }; diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx index 26de8c5f8b25a..607b3aeeac275 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx @@ -43,7 +43,6 @@ import { processImportResponse, ProcessedImportResponse, } from '../../../lib'; -import { ISavedObjectsManagementServiceRegistry } from '../../../services'; import { FailedImportConflict, RetryDecision } from '../../../lib/resolve_import_errors'; import { OverwriteModal } from './overwrite_modal'; import { ImportModeControl, ImportMode } from './import_mode_control'; @@ -53,7 +52,6 @@ const CREATE_NEW_COPIES_DEFAULT = false; const OVERWRITE_ALL_DEFAULT = true; export interface FlyoutProps { - serviceRegistry: ISavedObjectsManagementServiceRegistry; allowedTypes: string[]; close: () => void; done: () => void; diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx index 8c51a9a9c16e7..025a7a320327f 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx @@ -28,7 +28,6 @@ import { applicationServiceMock, } from '../../../../../core/public/mocks'; import { dataPluginMock } from '../../../../data/public/mocks'; -import { serviceRegistryMock } from '../../services/service_registry.mock'; import { actionServiceMock } from '../../services/action_service.mock'; import { columnServiceMock } from '../../services/column_service.mock'; import { @@ -122,7 +121,6 @@ describe('SavedObjectsTable', () => { defaultProps = { allowedTypes, - serviceRegistry: serviceRegistryMock.create(), actionRegistry: actionServiceMock.createStart(), columnRegistry: columnServiceMock.createStart(), savedObjectsClient: savedObjects.client, diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx index d4067cc21c2be..5001b52e819c2 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx @@ -37,7 +37,6 @@ import { } from '../../lib'; import { SavedObjectWithMetadata } from '../../types'; import { - ISavedObjectsManagementServiceRegistry, SavedObjectsManagementActionServiceStart, SavedObjectsManagementColumnServiceStart, } from '../../services'; @@ -58,7 +57,6 @@ interface ExportAllOption { export interface SavedObjectsTableProps { allowedTypes: string[]; - serviceRegistry: ISavedObjectsManagementServiceRegistry; actionRegistry: SavedObjectsManagementActionServiceStart; columnRegistry: SavedObjectsManagementColumnServiceStart; savedObjectsClient: SavedObjectsClientContract; @@ -540,7 +538,6 @@ export class SavedObjectsTable extends Component Date: Mon, 20 Sep 2021 16:41:35 -0700 Subject: [PATCH 15/18] moves inline style to scss file --- .../object_view/components/inspect.tsx | 9 ++++----- .../object_view/saved_object_view.scss | 3 +++ .../management_section/object_view/saved_object_view.tsx | 4 ++-- 3 files changed, 9 insertions(+), 7 deletions(-) create mode 100644 src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.scss diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/inspect.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/components/inspect.tsx index 1128ba3139762..cbb4ad6384505 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/components/inspect.tsx +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/inspect.tsx @@ -5,8 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - -import React, { FC } from 'react'; +import React from 'react'; import { i18n } from '@kbn/i18n'; import { XJsonLang } from '@kbn/monaco'; import { omit } from 'lodash'; @@ -19,7 +18,7 @@ export interface InspectProps { } const codeEditorAriaLabel = (title: string) => i18n.translate('savedObjectsManagement.view.inspectCodeEditorAriaLabel', { - defaultMessage: 'inspect { title } object', + defaultMessage: 'inspect { title }', values: { title, }, @@ -27,7 +26,7 @@ const codeEditorAriaLabel = (title: string) => const copyToClipboardLabel = i18n.translate('savedObjectsManagement.view.copyToClipboardLabel', { defaultMessage: 'Copy to clipboard', }); -export const Inspect: FC = ({ object }) => { +export const Inspect = ({ object }: InspectProps) => { const title = object.meta.title || 'saved object'; const objectAsJsonString = JSON.stringify(omit(object, 'meta'), null, 2); return ( @@ -53,7 +52,6 @@ export const Inspect: FC = ({ object }) => { languageId={XJsonLang.ID} value={objectAsJsonString} aria-label={codeEditorAriaLabel(title)} - height={'100%'} options={{ automaticLayout: false, fontSize: 12, @@ -69,6 +67,7 @@ export const Inspect: FC = ({ object }) => { scrollBeyondLastLine: false, wordWrap: 'on', wrappingIndent: 'indent', + renderIndentGuides: false, }} /> diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.scss b/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.scss new file mode 100644 index 0000000000000..656f93468db90 --- /dev/null +++ b/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.scss @@ -0,0 +1,3 @@ +.savedObjectsManagementObjectView { + height: 100%; +} diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.tsx index 8e5c5a0cb5ca5..e0cffa2ebd9fd 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.tsx +++ b/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.tsx @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - +import './saved_object_view.scss'; import React, { Component } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; @@ -102,7 +102,7 @@ export class SavedObjectEdition extends Component<
Date: Mon, 20 Sep 2021 16:48:53 -0700 Subject: [PATCH 16/18] Update snapshots --- .../__snapshots__/saved_object_view.test.tsx.snap | 6 +----- .../components/__snapshots__/inspect.test.tsx.snap | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/__snapshots__/saved_object_view.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/object_view/__snapshots__/saved_object_view.test.tsx.snap index 23df617b7eebd..8e3a954677b87 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/__snapshots__/saved_object_view.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/object_view/__snapshots__/saved_object_view.test.tsx.snap @@ -21,13 +21,9 @@ exports[`SavedObjectEdition should render normally 1`] = ` } > Date: Mon, 20 Sep 2021 17:18:43 -0700 Subject: [PATCH 17/18] Moves inline style to stylesheet --- .../management_section/saved_objects_edition_page.scss | 3 +++ .../management_section/saved_objects_edition_page.tsx | 7 +++++-- 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 src/plugins/saved_objects_management/public/management_section/saved_objects_edition_page.scss diff --git a/src/plugins/saved_objects_management/public/management_section/saved_objects_edition_page.scss b/src/plugins/saved_objects_management/public/management_section/saved_objects_edition_page.scss new file mode 100644 index 0000000000000..8938fac3aca53 --- /dev/null +++ b/src/plugins/saved_objects_management/public/management_section/saved_objects_edition_page.scss @@ -0,0 +1,3 @@ +.savedObjectsManagementEditionPage { + height: 100% +} diff --git a/src/plugins/saved_objects_management/public/management_section/saved_objects_edition_page.tsx b/src/plugins/saved_objects_management/public/management_section/saved_objects_edition_page.tsx index 2c154104b1c7e..4162ba6d0285d 100644 --- a/src/plugins/saved_objects_management/public/management_section/saved_objects_edition_page.tsx +++ b/src/plugins/saved_objects_management/public/management_section/saved_objects_edition_page.tsx @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - +import './saved_objects_edition_page.scss'; import React, { useEffect } from 'react'; import { useParams, useLocation } from 'react-router-dom'; import { parse } from 'query-string'; @@ -48,7 +48,10 @@ const SavedObjectsEditionPage = ({ }, [setBreadcrumbs, type]); return ( - + Date: Tue, 21 Sep 2021 10:54:22 -0700 Subject: [PATCH 18/18] Addresses nits --- .../object_view/components/inspect.tsx | 9 ++++-- .../object_view/saved_object_view.test.tsx | 32 ++++++++++++++----- .../object_view/saved_object_view.tsx | 4 +-- .../components/import_summary.tsx | 2 +- .../saved_objects_edition_page.tsx | 5 +-- .../saved_objects_table_page.tsx | 15 +++------ 6 files changed, 40 insertions(+), 27 deletions(-) diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/inspect.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/components/inspect.tsx index cbb4ad6384505..58d6da8ce935b 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/components/inspect.tsx +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/inspect.tsx @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import React from 'react'; +import React, { FC, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { XJsonLang } from '@kbn/monaco'; import { omit } from 'lodash'; @@ -26,9 +26,12 @@ const codeEditorAriaLabel = (title: string) => const copyToClipboardLabel = i18n.translate('savedObjectsManagement.view.copyToClipboardLabel', { defaultMessage: 'Copy to clipboard', }); -export const Inspect = ({ object }: InspectProps) => { + +export const Inspect: FC = ({ object }) => { const title = object.meta.title || 'saved object'; - const objectAsJsonString = JSON.stringify(omit(object, 'meta'), null, 2); + + const objectAsJsonString = useMemo(() => JSON.stringify(omit(object, 'meta'), null, 2), [object]); + return ( diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.test.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.test.tsx index 9ebb425a88ddb..13a806361e543 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.test.tsx +++ b/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.test.tsx @@ -29,6 +29,8 @@ import { SavedObjectEditionState, } from './saved_object_view'; +const resolvePromises = () => new Promise((resolve) => process.nextTick(resolve)); + describe('SavedObjectEdition', () => { let defaultProps: SavedObjectEditionProps; let http: ReturnType; @@ -110,7 +112,7 @@ describe('SavedObjectEdition', () => { ); const component = shallowRender(); // Ensure all promises resolve - await new Promise((resolve) => process.nextTick(resolve)); + await resolvePromises(); // Ensure the state changes are reflected component.update(); expect(component).toMatchSnapshot(); @@ -127,7 +129,9 @@ describe('SavedObjectEdition', () => { ]) ); const component = shallowRender({ notFoundType: 'does_not_exist' }); - await new Promise((resolve) => process.nextTick(resolve)); + + await resolvePromises(); + component.update(); expect(notifications.toasts.addDanger).toHaveBeenCalledTimes(1); @@ -136,7 +140,9 @@ describe('SavedObjectEdition', () => { it('should add danger toast when bulk get throws', async () => { bulkGetObjectsMock.mockImplementation(() => Promise.reject(new Error('fail'))); const component = shallowRender({ notFoundType: 'does_not_exist' }); - await new Promise((resolve) => process.nextTick(resolve)); + + await resolvePromises(); + component.update(); expect(notifications.toasts.addDanger).toHaveBeenCalledTimes(1); @@ -174,7 +180,9 @@ describe('SavedObjectEdition', () => { const component = shallowRender({ capabilities: applications.capabilities, }); - await new Promise((resolve) => process.nextTick(resolve)); + + await resolvePromises(); + component.update(); const headerComponent = component.find('Header'); expect(headerComponent.prop('canViewInApp')).toBe(true); @@ -199,7 +207,9 @@ describe('SavedObjectEdition', () => { const component = shallowRender({ capabilities: applications.capabilities, }); - await new Promise((resolve) => process.nextTick(resolve)); + + await resolvePromises(); + component.update(); const inspectComponent = component.find('Inspect'); expect(inspectComponent).toEqual({}); @@ -246,7 +256,9 @@ describe('SavedObjectEdition', () => { savedObjectsClient: mockSavedObjectsClient, overlays, }); - await new Promise((resolve) => process.nextTick(resolve)); + + await resolvePromises(); + component.update(); component.instance().delete(); expect(overlays.openConfirm).toHaveBeenCalledWith( @@ -281,7 +293,9 @@ describe('SavedObjectEdition', () => { savedObjectsClient: mockSavedObjectsClient, overlays, }); - await new Promise((resolve) => process.nextTick(resolve)); + + await resolvePromises(); + component.update(); component.instance().delete(); expect(overlays.openConfirm).toHaveBeenCalledTimes(1); @@ -305,7 +319,9 @@ describe('SavedObjectEdition', () => { const component = shallowRender({ capabilities: applications.capabilities, }); - await new Promise((resolve) => process.nextTick(resolve)); + + await resolvePromises(); + component.update(); expect(component.find('Header').prop('canDelete')).toBe(false); }); diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.tsx index e0cffa2ebd9fd..64b6e27309dd2 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.tsx +++ b/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.tsx @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import './saved_object_view.scss'; + import React, { Component } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; @@ -24,7 +24,7 @@ import { import { Header, Inspect, NotFoundErrors } from './components'; import { bulkGetObjects } from '../../lib/bulk_get_objects'; import { SavedObjectWithMetadata } from '../../types'; - +import './saved_object_view.scss'; export interface SavedObjectEditionProps { id: string; savedObjectType: string; diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/import_summary.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/import_summary.tsx index 8b07351f6c2c2..c24faf4e12687 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/import_summary.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/import_summary.tsx @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -import './import_summary.scss'; import _ from 'lodash'; import React, { Fragment, FC, useMemo } from 'react'; import { @@ -30,6 +29,7 @@ import type { IBasePath, } from 'kibana/public'; import { getDefaultTitle, getSavedObjectLabel, FailedImport } from '../../../lib'; +import './import_summary.scss'; const DEFAULT_ICON = 'apps'; diff --git a/src/plugins/saved_objects_management/public/management_section/saved_objects_edition_page.tsx b/src/plugins/saved_objects_management/public/management_section/saved_objects_edition_page.tsx index 4162ba6d0285d..74d27199770b1 100644 --- a/src/plugins/saved_objects_management/public/management_section/saved_objects_edition_page.tsx +++ b/src/plugins/saved_objects_management/public/management_section/saved_objects_edition_page.tsx @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import './saved_objects_edition_page.scss'; + import React, { useEffect } from 'react'; import { useParams, useLocation } from 'react-router-dom'; import { parse } from 'query-string'; @@ -13,6 +13,7 @@ import { i18n } from '@kbn/i18n'; import { CoreStart, ChromeBreadcrumb, ScopedHistory } from 'src/core/public'; import { RedirectAppLinks } from '../../../kibana_react/public'; import { SavedObjectEdition } from './object_view'; +import './saved_objects_edition_page.scss'; const SavedObjectsEditionPage = ({ coreStart, @@ -50,7 +51,7 @@ const SavedObjectsEditionPage = ({ return ( { - const { editUrl } = savedObject.meta; - if (editUrl) { - coreStart.application.navigateToUrl(coreStart.http.basePath.prepend(`/app${editUrl}`)); - } else { - coreStart.application.navigateToUrl( - coreStart.http.basePath.prepend( - `/app/management/kibana/objects/${savedObject.type}/${savedObject.id}` - ) - ); - } + const savedObjectEditUrl = savedObject.meta.editUrl + ? `/app${savedObject.meta.editUrl}` + : `/app/management/kibana/objects/${savedObject.type}/${savedObject.id}`; + coreStart.application.navigateToUrl(coreStart.http.basePath.prepend(savedObjectEditUrl)); }} canGoInApp={(savedObject) => { const { inAppUrl } = savedObject.meta;