From b6aab435f4fd26a1bd2fc0b65e4dda0847ee1fa3 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Thu, 5 Mar 2020 23:12:27 +0100 Subject: [PATCH 01/13] migrate so management edition view to react --- .../management/saved_object_registry.ts | 8 +- .../management/sections/objects/_objects.js | 1 - .../management/sections/objects/_view.html | 200 +----------- .../management/sections/objects/_view.js | 309 +++--------------- .../objects/components/object_view/field.tsx | 166 ++++++++++ .../objects/components/object_view/form.tsx | 270 +++++++++++++++ .../objects/components/object_view/header.tsx | 104 ++++++ .../objects/components/object_view/index.ts | 23 ++ .../objects/components/object_view/intro.tsx | 48 +++ .../object_view/not_found_errors.tsx | 79 +++++ .../lib/{in_app_url.js => in_app_url.ts} | 14 +- .../sections/objects/saved_object_view.tsx | 157 +++++++++ .../management/sections/objects/types.ts | 38 +++ 13 files changed, 946 insertions(+), 471 deletions(-) create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/field.tsx create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/form.tsx create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/header.tsx create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/index.ts create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/intro.tsx create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/not_found_errors.tsx rename src/legacy/core_plugins/kibana/public/management/sections/objects/lib/{in_app_url.js => in_app_url.ts} (71%) create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/objects/saved_object_view.tsx create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/objects/types.ts diff --git a/src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts b/src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts index 604575a6e6220..a3e0733500928 100644 --- a/src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts +++ b/src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts @@ -35,9 +35,15 @@ interface SavedObjectRegistryEntry { title: string; } +export interface ISavedObjectsManagementRegistry { + register(service: SavedObjectRegistryEntry): void; + all(): SavedObjectRegistryEntry[]; + get(id: string): SavedObjectRegistryEntry | undefined; +} + const registry: SavedObjectRegistryEntry[] = []; -export const savedObjectManagementRegistry = { +export const savedObjectManagementRegistry: ISavedObjectsManagementRegistry = { register: (service: SavedObjectRegistryEntry) => { registry.push(service); }, diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/_objects.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/_objects.js index e3ab862cd84b7..c5901ca6ee6bf 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/_objects.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/_objects.js @@ -28,7 +28,6 @@ import { ObjectsTable } from './components/objects_table'; import { I18nContext } from 'ui/i18n'; import { get } from 'lodash'; import { npStart } from 'ui/new_platform'; - import { getIndexBreadcrumbs } from './breadcrumbs'; const REACT_OBJECTS_TABLE_DOM_ELEMENT_ID = 'reactSavedObjectsTable'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/_view.html b/src/legacy/core_plugins/kibana/public/management/sections/objects/_view.html index 6efef7b48fa0e..0bc40db74c46f 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/_view.html +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/_view.html @@ -1,203 +1,5 @@ - -
-
-

- -

-
- -
- - - - - - - - -
-
- - -
-
-
- - -
- -
-
- -
- -
- -
-
-
-
- - -
-
-
- - -
- -
-
-
-
-
-
- -
- -
-
- - - - - - - - -
-
-
- - -
- - - -
-
+
diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/_view.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/_view.js index d1a8d6a1b14af..a847055b40015 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/_view.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/_view.js @@ -17,26 +17,20 @@ * under the License. */ -import _ from 'lodash'; -import { i18n } from '@kbn/i18n'; -import angular from 'angular'; +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import 'angular'; import 'angular-elastic/elastic'; -import rison from 'rison-node'; -import { savedObjectManagementRegistry } from '../../saved_object_registry'; -import objectViewHTML from './_view.html'; import uiRoutes from 'ui/routes'; import { uiModules } from 'ui/modules'; -import { fatalError, toastNotifications } from 'ui/notify'; -import 'ui/accessibility/kbn_ui_ace_keyboard_mode'; -import { isNumeric } from './lib/numeric'; -import { canViewInApp } from './lib/in_app_url'; +import { I18nContext } from 'ui/i18n'; import { npStart } from 'ui/new_platform'; - -import { castEsToKbnFieldTypeName } from '../../../../../../../plugins/data/public'; - +import objectViewHTML from './_view.html'; import { getViewBreadcrumbs } from './breadcrumbs'; +import { savedObjectManagementRegistry } from '../../saved_object_registry'; +import { SavedObjectEdition } from './saved_object_view'; -const location = 'SavedObject view'; +const REACT_OBJECTS_VIEW_DOM_ELEMENT_ID = 'reactSavedObjectsView'; uiRoutes.when('/management/kibana/objects/:service/:id', { template: objectViewHTML, @@ -44,261 +38,48 @@ uiRoutes.when('/management/kibana/objects/:service/:id', { requireUICapability: 'management.kibana.objects', }); +function createReactView($scope, $routeParams) { + const { service: serviceName, id: objectId, notFound } = $routeParams; + + const { savedObjects, overlays, notifications, application } = npStart.core; + + $scope.$$postDigest(() => { + const node = document.getElementById(REACT_OBJECTS_VIEW_DOM_ELEMENT_ID); + if (!node) { + return; + } + + render( + + + , + node + ); + }); +} + +function destroyReactView() { + const node = document.getElementById(REACT_OBJECTS_VIEW_DOM_ELEMENT_ID); + node && unmountComponentAtNode(node); +} + uiModules .get('apps/management', ['monospaced.elastic']) .directive('kbnManagementObjectsView', function() { return { restrict: 'E', - controller: function($scope, $routeParams, $location, $window, $rootScope, uiCapabilities) { - const serviceObj = savedObjectManagementRegistry.get($routeParams.service); - const service = serviceObj.service; - const savedObjectsClient = npStart.core.savedObjects.client; - const { overlays } = npStart.core; - - /** - * Creates a field definition and pushes it to the memo stack. This function - * is designed to be used in conjunction with _.reduce(). If the - * values is plain object it will recurse through all the keys till it hits - * a string, number or an array. - * - * @param {array} memo The stack of fields - * @param {mixed} value The value of the field - * @param {string} key The key of the field - * @param {object} collection This is a reference the collection being reduced - * @param {array} parents The parent keys to the field - * @returns {array} - */ - const createField = function(memo, val, key, collection, parents) { - if (Array.isArray(parents)) { - parents.push(key); - } else { - parents = [key]; - } - - const field = { type: 'text', name: parents.join('.'), value: val }; - - if (_.isString(field.value)) { - try { - field.value = angular.toJson(JSON.parse(field.value), true); - field.type = 'json'; - } catch (err) { - field.value = field.value; - } - } else if (isNumeric(field.value)) { - field.type = 'number'; - } else if (Array.isArray(field.value)) { - field.type = 'array'; - field.value = angular.toJson(field.value, true); - } else if (_.isBoolean(field.value)) { - field.type = 'boolean'; - field.value = field.value; - } else if (_.isPlainObject(field.value)) { - // do something recursive - return _.reduce(field.value, _.partialRight(createField, parents), memo); - } - - memo.push(field); - - // once the field is added to the object you need to pop the parents - // to remove it since we've hit the end of the branch. - parents.pop(); - return memo; - }; - - const readObjectClass = function(fields, Class) { - const fieldMap = _.indexBy(fields, 'name'); - - _.forOwn(Class.mapping, function(esType, name) { - if (fieldMap[name]) return; - - fields.push({ - name: name, - type: (function() { - switch (castEsToKbnFieldTypeName(esType)) { - case 'string': - return 'text'; - case 'number': - return 'number'; - case 'boolean': - return 'boolean'; - default: - return 'json'; - } - })(), - }); - }); - - if (Class.searchSource && !fieldMap['kibanaSavedObjectMeta.searchSourceJSON']) { - fields.push({ - name: 'kibanaSavedObjectMeta.searchSourceJSON', - type: 'json', - value: '{}', - }); - } - - if (!fieldMap.references) { - fields.push({ - name: 'references', - type: 'array', - value: '[]', - }); - } - }; - - const { edit: canEdit, delete: canDelete } = uiCapabilities.savedObjectsManagement; - $scope.canEdit = canEdit; - $scope.canDelete = canDelete; - $scope.canViewInApp = canViewInApp(uiCapabilities, service.type); - - $scope.notFound = $routeParams.notFound; - - $scope.title = service.type; - - savedObjectsClient - .get(service.type, $routeParams.id) - .then(function(obj) { - $scope.obj = obj; - $scope.link = service.urlFor(obj.id); - - const fields = _.reduce(obj.attributes, createField, []); - // Special handling for references which isn't within "attributes" - createField(fields, obj.references, 'references'); - - if (service.Class) readObjectClass(fields, service.Class); - - // sorts twice since we want numerical sort to prioritize over name, - // and sortBy will do string comparison if trying to match against strings - const nameSortedFields = _.sortBy(fields, 'name'); - $scope.$evalAsync(() => { - $scope.fields = _.sortBy(nameSortedFields, field => { - const orderIndex = service.Class.fieldOrder - ? service.Class.fieldOrder.indexOf(field.name) - : -1; - return orderIndex > -1 ? orderIndex : Infinity; - }); - }); - $scope.$digest(); - }) - .catch(error => fatalError(error, location)); - - // This handles the validation of the Ace Editor. Since we don't have any - // other hooks into the editors to tell us if the content is valid or not - // we need to use the annotations to see if they have any errors. If they - // do then we push the field.name to aceInvalidEditor variable. - // Otherwise we remove it. - const loadedEditors = []; - $scope.aceInvalidEditors = []; - - $scope.aceLoaded = function(editor) { - if (_.contains(loadedEditors, editor)) return; - loadedEditors.push(editor); - - editor.$blockScrolling = Infinity; - - const session = editor.getSession(); - const fieldName = editor.container.id; - - session.setTabSize(2); - session.setUseSoftTabs(true); - session.on('changeAnnotation', function() { - const annotations = session.getAnnotations(); - if (_.some(annotations, { type: 'error' })) { - if (!_.contains($scope.aceInvalidEditors, fieldName)) { - $scope.aceInvalidEditors.push(fieldName); - } - } else { - $scope.aceInvalidEditors = _.without($scope.aceInvalidEditors, fieldName); - } - - if (!$rootScope.$$phase) $scope.$apply(); - }); - }; - - $scope.cancel = function() { - $window.history.back(); - return false; - }; - - /** - * Deletes an object and sets the notification - * @param {type} name description - * @returns {type} description - */ - $scope.delete = function() { - function doDelete() { - savedObjectsClient - .delete(service.type, $routeParams.id) - .then(function() { - return redirectHandler('deleted'); - }) - .catch(error => fatalError(error, location)); - } - const confirmModalOptions = { - confirmButtonText: i18n.translate( - 'kbn.management.objects.confirmModalOptions.deleteButtonLabel', - { - defaultMessage: 'Delete', - } - ), - title: i18n.translate('kbn.management.objects.confirmModalOptions.modalTitle', { - defaultMessage: 'Delete saved Kibana object?', - }), - }; - - overlays - .openConfirm( - i18n.translate('kbn.management.objects.confirmModalOptions.modalDescription', { - defaultMessage: "You can't recover deleted objects", - }), - confirmModalOptions - ) - .then(isConfirmed => { - if (isConfirmed) { - doDelete(); - } - }); - }; - - $scope.submit = function() { - const source = _.cloneDeep($scope.obj.attributes); - - _.each($scope.fields, function(field) { - let value = field.value; - - if (field.type === 'number') { - value = Number(field.value); - } - - if (field.type === 'array') { - value = JSON.parse(field.value); - } - - _.set(source, field.name, value); - }); - - const { references, ...attributes } = source; - - savedObjectsClient - .update(service.type, $routeParams.id, attributes, { references }) - .then(function() { - return redirectHandler('updated'); - }) - .catch(error => fatalError(error, location)); - }; - - function redirectHandler(action) { - $location.path('/management/kibana/objects').search({ - _a: rison.encode({ - tab: serviceObj.title, - }), - }); - - toastNotifications.addSuccess( - `${_.capitalize(action)} '${ - $scope.obj.attributes.title - }' ${$scope.title.toLowerCase()} object` - ); - } + controller: function($scope, $routeParams) { + createReactView($scope, $routeParams); + $scope.$on('$destroy', destroyReactView); }, }; }); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/field.tsx b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/field.tsx new file mode 100644 index 0000000000000..a507b3db11cb8 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/field.tsx @@ -0,0 +1,166 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { PureComponent } from 'react'; +import { + EuiFieldNumber, + EuiFieldText, + EuiFormLabel, + EuiSwitch, + // @ts-ignore + 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 ( +
+ + {name} + + {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/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/form.tsx b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/form.tsx new file mode 100644 index 0000000000000..6d882f61f07cd --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/form.tsx @@ -0,0 +1,270 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { Component } from 'react'; +import { + forOwn, + indexBy, + cloneDeep, + isNumber, + isBoolean, + isPlainObject, + isString, + set, +} from 'lodash'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + SimpleSavedObject, + SavedObjectsClientContract, +} from '../../../../../../../../../core/public'; +import { castEsToKbnFieldTypeName } from '../../../../../../../../../plugins/data/public'; +import { SavedObjectLoader } from '../../../../../../../../../plugins/saved_objects/public'; +import { Field } from './field'; +import { ObjectField, FieldState, SubmittedFormData } from '../../types'; + +interface FormProps { + object: SimpleSavedObject; + service: SavedObjectLoader; + savedObjectsClient: SavedObjectsClientContract; + editionEnabled: boolean; + onSave: (form: SubmittedFormData) => void; +} + +interface FormState { + fields: ObjectField[]; + fieldStates: Record; +} + +export class Form extends Component { + constructor(props: FormProps) { + super(props); + this.state = { + fields: [], + fieldStates: {}, + }; + } + + componentDidMount() { + const { object, service } = this.props; + + const fields = Object.entries(object.attributes as Record).reduce( + (objFields, [key, value]) => { + return [...objFields, ...recursiveCreateFields(key, value)]; + }, + [] as ObjectField[] + ); + if ((service as any).Class) { + addFieldsFromClass((service as any).Class, fields); + } + + this.setState({ + fields, + }); + } + + render() { + const { editionEnabled, service } = this.props; + const { fields, fieldStates } = 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; + } + + 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); + }); + + const { references, ...attributes } = source; + + onSave({ attributes, references }); + }; +} + +/** + * Creates a field definition and pushes it to the memo stack. This function + * is designed to be used in conjunction with _.reduce(). If the + * values is plain object it will recurse through all the keys till it hits + * a string, number or an array. + * + * @param {string} key The key of the field + * @param {mixed} value The value of the field + * @param {array} parents The parent keys to the field + * @returns {array} + */ +const recursiveCreateFields = (key: string, value: any, parents: string[] = []): ObjectField[] => { + const path = [...parents, key]; + + const field: ObjectField = { type: 'text', name: path.join('.'), value }; + let fields: ObjectField[] = [field]; + + if (isString(field.value)) { + try { + field.value = JSON.stringify(JSON.parse(field.value), undefined, 2); + field.type = 'json'; + } catch (err) { + field.type = 'text'; + } + } else if (isNumber(field.value)) { + field.type = 'number'; + } else if (Array.isArray(field.value)) { + field.type = 'array'; + field.value = JSON.stringify(field.value, undefined, 2); + } else if (isBoolean(field.value)) { + field.type = 'boolean'; + } else if (isPlainObject(field.value)) { + forOwn(field.value, (childValue, childKey) => { + fields = [...recursiveCreateFields(childKey as string, childValue, path)]; + }); + } + + return fields; +}; + +const addFieldsFromClass = function( + Class: { mapping: Record; searchSource: any }, + fields: ObjectField[] +) { + const fieldMap = indexBy(fields, 'name'); + + _.forOwn(Class.mapping, (esType, name) => { + if (!name || fieldMap[name]) { + return; + } + + const getFieldTypeFromEsType = () => { + switch (castEsToKbnFieldTypeName(esType)) { + case 'string': + return 'text'; + case 'number': + return 'number'; + case 'boolean': + return 'boolean'; + default: + return 'json'; + } + }; + + fields.push({ + name, + type: getFieldTypeFromEsType(), + value: undefined, + }); + }); + + if (Class.searchSource && !fieldMap['kibanaSavedObjectMeta.searchSourceJSON']) { + fields.push({ + name: 'kibanaSavedObjectMeta.searchSourceJSON', + type: 'json', + value: '{}', + }); + } + + if (!fieldMap.references) { + fields.push({ + name: 'references', + type: 'array', + value: '[]', + }); + } +}; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/header.tsx b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/header.tsx new file mode 100644 index 0000000000000..77fe04381c1eb --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/header.tsx @@ -0,0 +1,104 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +interface HeaderProps { + canEdit: boolean; + canDelete: boolean; + canViewInApp: boolean; + type: string; + viewUrl: string; + onDeleteClick: () => void; +} + +export const Header = ({ + canEdit, + canDelete, + canViewInApp, + type, + viewUrl, + onDeleteClick, +}: HeaderProps) => { + return ( +
+
+

+ {canEdit ? ( + + ) : ( + + )} +

+
+ +
+ {canViewInApp && ( + + + + + + + + + )} + + {canDelete && ( + + )} +
+
+ ); +}; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/index.ts b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/index.ts new file mode 100644 index 0000000000000..a3a05c05cb4a9 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/index.ts @@ -0,0 +1,23 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { Header } from './header'; +export { NotFoundErrors } from './not_found_errors'; +export { Intro } from './intro'; +export { Form } from './form'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/intro.tsx b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/intro.tsx new file mode 100644 index 0000000000000..f481d9a678333 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/intro.tsx @@ -0,0 +1,48 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; + +export const Intro = () => { + return ( +
+
+
+ + + + +
+ +
+
+ +
+
+
+
+ ); +}; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/not_found_errors.tsx b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/not_found_errors.tsx new file mode 100644 index 0000000000000..f79e5f5afd71f --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/not_found_errors.tsx @@ -0,0 +1,79 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; + +interface NotFoundErrors { + type: string; +} + +export const NotFoundErrors = ({ type }: NotFoundErrors) => { + return ( +
+
+
+ + + + +
+ +
+ {type === 'search' && ( +
+ +
+ )} + + {type === 'index-pattern' && ( +
+ +
+ )} + + {type === 'index-pattern-field' && ( +
+ +
+ )} + +
+ +
+
+
+
+ ); +}; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/in_app_url.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/in_app_url.ts similarity index 71% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/lib/in_app_url.js rename to src/legacy/core_plugins/kibana/public/management/sections/objects/lib/in_app_url.ts index 0026154848693..82146554afa6f 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/in_app_url.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/in_app_url.ts @@ -17,22 +17,24 @@ * under the License. */ -export function canViewInApp(uiCapabilities, type) { +import { Capabilities } from 'src/core/public'; + +export function canViewInApp(uiCapabilities: Capabilities, type: string): boolean { switch (type) { case 'search': case 'searches': - return uiCapabilities.discover.show; + return uiCapabilities.discover.show as boolean; case 'visualization': case 'visualizations': - return uiCapabilities.visualize.show; + return uiCapabilities.visualize.show as boolean; case 'index-pattern': case 'index-patterns': case 'indexPatterns': - return uiCapabilities.management.kibana.index_patterns; + return uiCapabilities.management.kibana.index_patterns as boolean; case 'dashboard': case 'dashboards': - return uiCapabilities.dashboard.show; + return uiCapabilities.dashboard.show as boolean; default: - return uiCapabilities[type].show; + return uiCapabilities[type].show as boolean; } } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/saved_object_view.tsx b/src/legacy/core_plugins/kibana/public/management/sections/objects/saved_object_view.tsx new file mode 100644 index 0000000000000..37338777385ad --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/saved_object_view.tsx @@ -0,0 +1,157 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { Component } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + Capabilities, + SavedObjectsClientContract, + OverlayStart, + NotificationsStart, + SimpleSavedObject, +} from '../../../../../../../core/public'; +import { ISavedObjectsManagementRegistry } from '../../saved_object_registry'; +import { Header, NotFoundErrors, Intro, Form } from './components/object_view'; +import { canViewInApp } from './lib/in_app_url'; +import { SubmittedFormData } from './types'; + +interface SavedObjectEditionProps { + id: string; + serviceName: string; + serviceRegistry: ISavedObjectsManagementRegistry; + capabilities: Capabilities; + overlays: OverlayStart; + notifications: NotificationsStart; + notFoundType?: string; + savedObjectsClient: SavedObjectsClientContract; +} + +interface SavedObjectEditionState { + type: string; + object?: SimpleSavedObject; +} + +export class SavedObjectEdition extends Component< + SavedObjectEditionProps, + SavedObjectEditionState +> { + constructor(props: SavedObjectEditionProps) { + super(props); + + const { serviceRegistry, serviceName } = props; + const type = serviceRegistry.get(serviceName)!.service.type; + + this.state = { + object: undefined, + type, + }; + } + + componentDidMount() { + const { id, savedObjectsClient } = this.props; + const { type } = this.state; + savedObjectsClient.get(type, id).then(object => { + this.setState({ + object, + }); + }); + } + + render() { + const { + capabilities, + notFoundType, + serviceRegistry, + id, + serviceName, + savedObjectsClient, + } = this.props; + const { type } = this.state; + const { object } = this.state; + const { edit: canEdit, delete: canDelete } = capabilities.savedObjectsManagement as Record< + string, + boolean + >; + const canView = canViewInApp(capabilities, type); + const service = serviceRegistry.get(serviceName)!.service; + + return ( +
+
this.delete()} + viewUrl={service.urlFor(id)} + /> + {notFoundType && } + {canEdit && } + {object && ( +
+ )} +
+ ); + } + + async delete() { + const { id, savedObjectsClient, overlays, notifications } = this.props; + const { type, object } = this.state; + + const confirmed = await overlays.openConfirm( + i18n.translate('kbn.management.objects.confirmModalOptions.modalDescription', { + defaultMessage: "You can't recover deleted objects", + }), + { + confirmButtonText: i18n.translate( + 'kbn.management.objects.confirmModalOptions.deleteButtonLabel', + { + defaultMessage: 'Delete', + } + ), + title: i18n.translate('kbn.management.objects.confirmModalOptions.modalTitle', { + defaultMessage: 'Delete saved Kibana object?', + }), + } + ); + if (confirmed) { + await savedObjectsClient.delete(type, id); + notifications.toasts.addSuccess(`Deleted '${object!.attributes.title}' ${type} object`); + window.location.hash = '/management/kibana/objects'; + } + } + + 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`); + window.location.hash = '/management/kibana/objects'; + }; +} diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/types.ts b/src/legacy/core_plugins/kibana/public/management/sections/objects/types.ts new file mode 100644 index 0000000000000..32436e96d4829 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/types.ts @@ -0,0 +1,38 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { SavedObjectReference } from 'src/core/public'; + +export interface ObjectField { + type: FieldType; + name: string; + value: any; +} + +export type FieldType = 'text' | 'number' | 'boolean' | 'array' | 'json'; + +export interface FieldState { + value?: any; + invalid?: boolean; +} + +export interface SubmittedFormData { + attributes: any; + references: SavedObjectReference[]; +} From 81c0623bb08f8bda805402a03babaafce94fdc13 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Fri, 6 Mar 2020 07:54:55 +0100 Subject: [PATCH 02/13] fix bundle name + add forgotten data-test-subj --- .../sections/objects/components/object_view/field.tsx | 4 ++-- .../sections/objects/components/object_view/form.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/field.tsx b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/field.tsx index a507b3db11cb8..cc5a7ba72268f 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/field.tsx +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/field.tsx @@ -107,9 +107,9 @@ export class Field extends PureComponent { id={this.fieldId} label={ !!currentValue ? ( - + ) : ( - + ) } checked={!!currentValue} diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/form.tsx b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/form.tsx index 6d882f61f07cd..18ffb0bec2a44 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/form.tsx +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/form.tsx @@ -84,7 +84,7 @@ export class Form extends Component { const { fields, fieldStates } = this.state; const isValid = this.isFormValid(); return ( -
+
{fields.map(field => ( Date: Fri, 6 Mar 2020 09:30:37 +0100 Subject: [PATCH 03/13] add FTR tests for edition page --- .../edit_saved_object.ts | 103 ++++ .../apps/saved_objects_management/index.ts | 27 ++ test/functional/config.js | 1 + .../edit_saved_object/data.json | 85 ++++ .../edit_saved_object/mappings.json | 459 ++++++++++++++++++ 5 files changed, 675 insertions(+) create mode 100644 test/functional/apps/saved_objects_management/edit_saved_object.ts create mode 100644 test/functional/apps/saved_objects_management/index.ts create mode 100644 test/functional/fixtures/es_archiver/saved_objects_management/edit_saved_object/data.json create mode 100644 test/functional/fixtures/es_archiver/saved_objects_management/edit_saved_object/mappings.json diff --git a/test/functional/apps/saved_objects_management/edit_saved_object.ts b/test/functional/apps/saved_objects_management/edit_saved_object.ts new file mode 100644 index 0000000000000..c001613f09f49 --- /dev/null +++ b/test/functional/apps/saved_objects_management/edit_saved_object.ts @@ -0,0 +1,103 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + +export default function({ getPageObjects, getService }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const testSubjects = getService('testSubjects'); + const PageObjects = getPageObjects(['common', 'settings']); + + 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 focusAndClickButton = async (buttonSubject: string) => { + const button = await testSubjects.find(buttonSubject); + await button.scrollIntoViewIfNecessary(); + await delay(10); + await button.focus(); + await delay(10); + await button.click(); + }; + + describe('TOTO saved objects edition page', () => { + beforeEach(async () => { + await esArchiver.load('saved_objects_management/edit_saved_object'); + }); + + afterEach(async () => { + await esArchiver.unload('saved_objects_management/edit_saved_object'); + }); + + it('allows to update the saved object when submitting', async () => { + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaSavedObjects(); + + let objects = await PageObjects.settings.getSavedObjectsInTable(); + expect(objects.includes('A Dashboard')).to.be(true); + + await PageObjects.common.navigateToActualUrl( + 'kibana', + '/management/kibana/objects/savedDashboards/i-exist' + ); + + 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.settings.getSavedObjectsInTable(); + expect(objects.includes('A Dashboard')).to.be(false); + expect(objects.includes('Edited Dashboard')).to.be(true); + + await PageObjects.common.navigateToActualUrl( + 'kibana', + '/management/kibana/objects/savedDashboards/i-exist' + ); + + expect(await getFieldValue('title')).to.eql('Edited Dashboard'); + expect(await getFieldValue('description')).to.eql('Some description'); + }); + + it('allows to delete a saved object', async () => { + await PageObjects.common.navigateToActualUrl( + 'kibana', + '/management/kibana/objects/savedDashboards/i-exist' + ); + + await focusAndClickButton('savedObjectEditDelete'); + await PageObjects.common.clickConfirmOnModal(); + + const objects = await PageObjects.settings.getSavedObjectsInTable(); + expect(objects.includes('A Dashboard')).to.be(false); + }); + }); +} diff --git a/test/functional/apps/saved_objects_management/index.ts b/test/functional/apps/saved_objects_management/index.ts new file mode 100644 index 0000000000000..ab43e18735ee7 --- /dev/null +++ b/test/functional/apps/saved_objects_management/index.ts @@ -0,0 +1,27 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +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')); + }); +} diff --git a/test/functional/config.js b/test/functional/config.js index e84b7e0a98a68..36b882d72679b 100644 --- a/test/functional/config.js +++ b/test/functional/config.js @@ -32,6 +32,7 @@ export default async function({ readConfigFile }) { require.resolve('./apps/discover'), require.resolve('./apps/home'), require.resolve('./apps/management'), + require.resolve('./apps/saved_objects_management'), require.resolve('./apps/status_page'), require.resolve('./apps/timelion'), require.resolve('./apps/visualize'), diff --git a/test/functional/fixtures/es_archiver/saved_objects_management/edit_saved_object/data.json b/test/functional/fixtures/es_archiver/saved_objects_management/edit_saved_object/data.json new file mode 100644 index 0000000000000..f085bad4c507e --- /dev/null +++ b/test/functional/fixtures/es_archiver/saved_objects_management/edit_saved_object/data.json @@ -0,0 +1,85 @@ +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "index-pattern:logstash-*", + "source": { + "index-pattern": { + "title": "logstash-*", + "timeFieldName": "@timestamp", + "fields": "[{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]" + }, + "type": "index-pattern", + "migrationVersion": { + "index-pattern": "6.5.0" + }, + "updated_at": "2018-12-21T00:43:07.096Z" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "visualization:75c3e060-1e7c-11e9-8488-65449e65d0ed", + "source": { + "visualization": { + "title": "A Pie", + "visState": "{\"title\":\"A Pie\",\"type\":\"pie\",\"params\":{\"type\":\"pie\",\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"isDonut\":true,\"labels\":{\"show\":false,\"values\":true,\"last_level\":true,\"truncate\":100},\"dimensions\":{\"metric\":{\"accessor\":0,\"format\":{\"id\":\"number\"},\"params\":{},\"aggType\":\"count\"}}},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"segment\",\"params\":{\"field\":\"geo.src\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\",\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\"}}]}", + "uiStateJSON": "{}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"logstash-*\",\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[]}" + } + }, + "type": "visualization", + "updated_at": "2019-01-22T19:32:31.206Z" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "dashboard:i-exist", + "source": { + "dashboard": { + "title": "A Dashboard", + "hits": 0, + "description": "", + "panelsJSON": "[{\"gridData\":{\"w\":24,\"h\":15,\"x\":0,\"y\":0,\"i\":\"1\"},\"version\":\"7.0.0\",\"panelIndex\":\"1\",\"type\":\"visualization\",\"id\":\"75c3e060-1e7c-11e9-8488-65449e65d0ed\",\"embeddableConfig\":{}}]", + "optionsJSON": "{\"darkTheme\":false,\"useMargins\":true,\"hidePanelTitles\":false}", + "version": 1, + "timeRestore": false, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[]}" + } + }, + "type": "dashboard", + "updated_at": "2019-01-22T19:32:47.232Z" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "config:6.0.0", + "source": { + "config": { + "buildNum": 9007199254740991, + "defaultIndex": "logstash-*" + }, + "type": "config", + "updated_at": "2019-01-22T19:32:02.235Z" + } + } +} diff --git a/test/functional/fixtures/es_archiver/saved_objects_management/edit_saved_object/mappings.json b/test/functional/fixtures/es_archiver/saved_objects_management/edit_saved_object/mappings.json new file mode 100644 index 0000000000000..96e6b7c0a19f1 --- /dev/null +++ b/test/functional/fixtures/es_archiver/saved_objects_management/edit_saved_object/mappings.json @@ -0,0 +1,459 @@ +{ + "type": "index", + "value": { + "index": ".kibana", + "settings": { + "index": { + "number_of_shards": "1", + "auto_expand_replicas": "0-1", + "number_of_replicas": "0" + } + }, + "mappings": { + "dynamic": "strict", + "properties": { + "apm-telemetry": { + "properties": { + "has_any_services": { + "type": "boolean" + }, + "services_per_agent": { + "properties": { + "go": { + "type": "long", + "null_value": 0 + }, + "java": { + "type": "long", + "null_value": 0 + }, + "js-base": { + "type": "long", + "null_value": 0 + }, + "nodejs": { + "type": "long", + "null_value": 0 + }, + "python": { + "type": "long", + "null_value": 0 + }, + "ruby": { + "type": "long", + "null_value": 0 + } + } + } + } + }, + "canvas-workpad": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "id": { + "type": "text", + "index": false + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + } + } + }, + "config": { + "dynamic": "true", + "properties": { + "accessibility:disableAnimations": { + "type": "boolean" + }, + "buildNum": { + "type": "keyword" + }, + "dateFormat:tz": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "defaultIndex": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "telemetry:optIn": { + "type": "boolean" + } + } + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "map": { + "properties": { + "bounds": { + "type": "geo_shape", + "tree": "quadtree" + }, + "description": { + "type": "text" + }, + "layerListJSON": { + "type": "text" + }, + "mapStateJSON": { + "type": "text" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "graph-workspace": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "numLinks": { + "type": "integer" + }, + "numVertices": { + "type": "integer" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "wsState": { + "type": "text" + } + } + }, + "index-pattern": { + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "typeMeta": { + "type": "keyword" + } + } + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "index-pattern": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "space": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + }, + "namespace": { + "type": "keyword" + }, + "search": { + "properties": { + "columns": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "sort": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "server": { + "properties": { + "uuid": { + "type": "keyword" + } + } + }, + "space": { + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "disabledFeatures": { + "type": "keyword" + }, + "initials": { + "type": "keyword" + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 2048 + } + } + } + } + }, + "spaceId": { + "type": "keyword" + }, + "telemetry": { + "properties": { + "enabled": { + "type": "boolean" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 2048 + } + } + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchId": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + } + } + } + } +} \ No newline at end of file From 4f60ecf286d96ea3511dcc98207acbf5feee2ce9 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Fri, 6 Mar 2020 12:51:39 +0100 Subject: [PATCH 04/13] EUIfy react components --- .../objects/components/object_view/form.tsx | 87 ++++++++------ .../objects/components/object_view/header.tsx | 106 +++++++++--------- .../objects/components/object_view/intro.tsx | 38 +++---- .../object_view/not_found_errors.tsx | 94 ++++++++-------- .../sections/objects/saved_object_view.tsx | 32 ++++-- 5 files changed, 187 insertions(+), 170 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/form.tsx b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/form.tsx index 18ffb0bec2a44..8e3d59b7e3fdd 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/form.tsx +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/form.tsx @@ -18,6 +18,14 @@ */ import React, { Component } from 'react'; +import { + EuiForm, + EuiFormRow, + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiButtonEmpty, +} from '@elastic/eui'; import { forOwn, indexBy, @@ -84,7 +92,7 @@ export class Form extends Component { const { fields, fieldStates } = this.state; const isValid = this.isFormValid(); return ( -
+
{fields.map(field => ( { /> ))}
-
- {editionEnabled && ( - - )} + + + {editionEnabled && ( + + + + + + )} - -
-
+ + + + + + + + ); } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/header.tsx b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/header.tsx index 77fe04381c1eb..0443cd41b79b5 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/header.tsx +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/header.tsx @@ -18,7 +18,9 @@ */ import React from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiButton } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; + interface HeaderProps { canEdit: boolean; canDelete: boolean; @@ -37,68 +39,62 @@ export const Header = ({ onDeleteClick, }: HeaderProps) => { return ( -
-
-

- {canEdit ? ( - - ) : ( - - )} -

-
- -
- {canViewInApp && ( - - - - + + + {canEdit ? ( + +

+ +

+
+ ) : ( + +

+ +

+
+ )} +
+ + + {canViewInApp && ( + + -
-
-
- )} - - {canDelete && ( - - )} -
-
+ + + )} + + + ); }; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/intro.tsx b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/intro.tsx index f481d9a678333..fb25ba2e1de8a 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/intro.tsx +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/intro.tsx @@ -18,31 +18,27 @@ */ import React from 'react'; +import { EuiCallOut } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; export const Intro = () => { return ( -
-
-
- - - - -
- -
-
- -
-
+ + } + iconType="bolt" + color="warning" + > +
+
-
+ ); }; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/not_found_errors.tsx b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/not_found_errors.tsx index f79e5f5afd71f..c3d18855f6c9a 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/not_found_errors.tsx +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/not_found_errors.tsx @@ -18,6 +18,7 @@ */ import React from 'react'; +import { EuiCallOut } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; interface NotFoundErrors { @@ -25,55 +26,52 @@ interface NotFoundErrors { } export const NotFoundErrors = ({ type }: NotFoundErrors) => { - return ( -
-
-
- - - - -
- -
- {type === 'search' && ( -
- -
- )} - - {type === 'index-pattern' && ( -
- -
- )} + const getMessage = () => { + switch (type) { + case 'search': + return ( + + ); + case 'index-pattern': + return ( + + ); + case 'index-pattern-field': + return ( + + ); + default: + return null; + } + }; - {type === 'index-pattern-field' && ( -
- -
- )} - -
- -
-
+ return ( + + } + iconType="alert" + color="danger" + > +
{getMessage()}
+
+
-
+ ); }; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/saved_object_view.tsx b/src/legacy/core_plugins/kibana/public/management/sections/objects/saved_object_view.tsx index 37338777385ad..5bdb86e6aa8aa 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/saved_object_view.tsx +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/saved_object_view.tsx @@ -19,6 +19,7 @@ import React, { Component } from 'react'; import { i18n } from '@kbn/i18n'; +import { EuiSpacer } from '@elastic/eui'; import { Capabilities, SavedObjectsClientContract, @@ -104,16 +105,29 @@ export class SavedObjectEdition extends Component< onDeleteClick={() => this.delete()} viewUrl={service.urlFor(id)} /> - {notFoundType && } - {canEdit && } + {notFoundType && ( + <> + + + + )} + {canEdit && ( + <> + + + + )} {object && ( - + <> + + + )}
); From 330a0c0f8b758cc6205bb7bb314e6ddebea2a084 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Fri, 6 Mar 2020 18:31:17 +0100 Subject: [PATCH 05/13] wrap form with EuiPanel + caps btns labels --- .../objects/components/object_view/form.tsx | 103 +++++++++--------- 1 file changed, 53 insertions(+), 50 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/form.tsx b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/form.tsx index 8e3d59b7e3fdd..5cf8338e0cff0 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/form.tsx +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/form.tsx @@ -19,6 +19,7 @@ import React, { Component } from 'react'; import { + EuiPanel, EuiForm, EuiFormRow, EuiFlexGroup, @@ -92,62 +93,64 @@ export class Form extends Component { const { fields, fieldStates } = this.state; const isValid = this.isFormValid(); return ( - -
- {fields.map(field => ( - - ))} -
- - - {editionEnabled && ( + + +
+ {fields.map(field => ( + + ))} +
+ + + {editionEnabled && ( + + + + + + )} + - - + - )} - - - - - - - - -
+
+
+
+ ); } From 6e049cf1c12fada17a606449346df7ce41e2d00d Mon Sep 17 00:00:00 2001 From: cchaos Date: Mon, 9 Mar 2020 14:31:11 -0400 Subject: [PATCH 06/13] Wrapping whole view in page content panel and removing legacy classes --- .../management/sections/objects/_view.html | 4 +- .../objects/components/object_view/field.tsx | 1 - .../objects/components/object_view/form.tsx | 103 +++++++++--------- .../objects/components/object_view/header.tsx | 39 ++++--- .../sections/objects/saved_object_view.tsx | 9 +- 5 files changed, 77 insertions(+), 79 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/_view.html b/src/legacy/core_plugins/kibana/public/management/sections/objects/_view.html index 0bc40db74c46f..8bce0aabcd64a 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/_view.html +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/_view.html @@ -1,5 +1,5 @@ - - + +
diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/field.tsx b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/field.tsx index cc5a7ba72268f..e08122603285b 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/field.tsx +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/field.tsx @@ -123,7 +123,6 @@ export class Field extends PureComponent { return (
{ const { fields, fieldStates } = this.state; const isValid = this.isFormValid(); return ( - - -
- {fields.map(field => ( - - ))} -
- - - {editionEnabled && ( - - - - - - )} - + +
+ {fields.map(field => ( + + ))} +
+ + + {editionEnabled && ( - - + - - -
-
+ )} + + + + + + + + + ); } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/header.tsx b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/header.tsx index 0443cd41b79b5..98efa96d68de4 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/header.tsx +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/header.tsx @@ -18,7 +18,14 @@ */ import React from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiButton } from '@elastic/eui'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiTitle, + EuiButton, + EuiPageContentHeader, + EuiPageContentHeaderSection, +} from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; interface HeaderProps { @@ -39,10 +46,10 @@ export const Header = ({ onDeleteClick, }: HeaderProps) => { return ( - - - {canEdit ? ( - + + + + {canEdit ? (

-
- ) : ( - + ) : (

-
- )} -
- - + )} + + + + {canViewInApp && ( - + )} {canDelete && ( - + )} - -
+ + ); }; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/saved_object_view.tsx b/src/legacy/core_plugins/kibana/public/management/sections/objects/saved_object_view.tsx index 5bdb86e6aa8aa..066c1a58577a9 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/saved_object_view.tsx +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/saved_object_view.tsx @@ -19,7 +19,7 @@ import React, { Component } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiSpacer } from '@elastic/eui'; +import { EuiSpacer, EuiPageContent } from '@elastic/eui'; import { Capabilities, SavedObjectsClientContract, @@ -93,10 +93,7 @@ export class SavedObjectEdition extends Component< const service = serviceRegistry.get(serviceName)!.service; return ( -
+
)} -
+ ); } From 79e2c9f508e29c5663b26788b643313aabd86400 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Tue, 10 Mar 2020 08:48:19 +0100 Subject: [PATCH 07/13] improve delete confirmation modal --- src/core/public/overlays/modal/modal_service.tsx | 1 + .../sections/objects/saved_object_view.tsx | 14 +++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/core/public/overlays/modal/modal_service.tsx b/src/core/public/overlays/modal/modal_service.tsx index 3cf1fe745be8e..f3bbd5c94bdb4 100644 --- a/src/core/public/overlays/modal/modal_service.tsx +++ b/src/core/public/overlays/modal/modal_service.tsx @@ -69,6 +69,7 @@ export interface OverlayModalConfirmOptions { closeButtonAriaLabel?: string; 'data-test-subj'?: string; defaultFocusedButton?: EuiConfirmModalProps['defaultFocusedButton']; + buttonColor?: EuiConfirmModalProps['buttonColor']; } /** diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/saved_object_view.tsx b/src/legacy/core_plugins/kibana/public/management/sections/objects/saved_object_view.tsx index 066c1a58577a9..d544d9d99c670 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/saved_object_view.tsx +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/saved_object_view.tsx @@ -146,14 +146,18 @@ export class SavedObjectEdition extends Component< } ), title: i18n.translate('kbn.management.objects.confirmModalOptions.modalTitle', { - defaultMessage: 'Delete saved Kibana object?', + defaultMessage: 'Delete {title}?', + values: { + title: object?.attributes?.title || 'saved Kibana object', + }, }), + buttonColor: 'danger', } ); if (confirmed) { await savedObjectsClient.delete(type, id); notifications.toasts.addSuccess(`Deleted '${object!.attributes.title}' ${type} object`); - window.location.hash = '/management/kibana/objects'; + this.redirectToListing(); } } @@ -163,6 +167,10 @@ export class SavedObjectEdition extends Component< await savedObjectsClient.update(object!.type, object!.id, attributes, { references }); notifications.toasts.addSuccess(`Updated '${attributes.title}' ${type} object`); - window.location.hash = '/management/kibana/objects'; + this.redirectToListing(); }; + + redirectToListing() { + window.location.hash = '/management/kibana/objects'; + } } From 1d7c60f14f0a69684b3d65da0b01eb4970324622 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Tue, 10 Mar 2020 09:51:45 +0100 Subject: [PATCH 08/13] update translations --- x-pack/plugins/translations/translations/ja-JP.json | 2 +- x-pack/plugins/translations/translations/zh-CN.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 5635bb19b7e83..733172706a601 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -1345,7 +1345,7 @@ "kbn.management.landing.text": "すべてのツールの一覧は、左のメニューにあります。", "kbn.management.objects.confirmModalOptions.deleteButtonLabel": "削除", "kbn.management.objects.confirmModalOptions.modalDescription": "削除されたオブジェクトは復元できません", - "kbn.management.objects.confirmModalOptions.modalTitle": "保存された Kibana オブジェクトを削除しますか?", + "kbn.management.objects.confirmModalOptions.modalTitle": "{title} を削除しますか?", "kbn.management.objects.deleteSavedObjectsConfirmModalDescription": "この操作は次の保存されたオブジェクトを削除します:", "kbn.management.objects.objectsTable.deleteSavedObjectsConfirmModal.cancelButtonLabel": "キャンセル", "kbn.management.objects.objectsTable.deleteSavedObjectsConfirmModal.deleteButtonLabel": "削除", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 0523021046167..2b101e2ac32ce 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -1345,7 +1345,7 @@ "kbn.management.landing.text": "在左侧菜单中可找到完整工具列表", "kbn.management.objects.confirmModalOptions.deleteButtonLabel": "删除", "kbn.management.objects.confirmModalOptions.modalDescription": "您无法恢复删除的对象", - "kbn.management.objects.confirmModalOptions.modalTitle": "删除已保存 Kibana 对象?", + "kbn.management.objects.confirmModalOptions.modalTitle": "删除 {title}?", "kbn.management.objects.deleteSavedObjectsConfirmModalDescription": "此操作将删除以下已保存对象:", "kbn.management.objects.objectsTable.deleteSavedObjectsConfirmModal.cancelButtonLabel": "取消", "kbn.management.objects.objectsTable.deleteSavedObjectsConfirmModal.deleteButtonLabel": "删除", From 0015f2aa854686f364c445d2a78e1289866fb990 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Wed, 11 Mar 2020 11:19:57 +0100 Subject: [PATCH 09/13] improve delete popin --- .../public/management/sections/objects/saved_object_view.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/saved_object_view.tsx b/src/legacy/core_plugins/kibana/public/management/sections/objects/saved_object_view.tsx index d544d9d99c670..4984fe3e6d6b8 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/saved_object_view.tsx +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/saved_object_view.tsx @@ -136,7 +136,7 @@ export class SavedObjectEdition extends Component< const confirmed = await overlays.openConfirm( i18n.translate('kbn.management.objects.confirmModalOptions.modalDescription', { - defaultMessage: "You can't recover deleted objects", + defaultMessage: 'This action permanently removes the object from Kibana.', }), { confirmButtonText: i18n.translate( @@ -146,7 +146,7 @@ export class SavedObjectEdition extends Component< } ), title: i18n.translate('kbn.management.objects.confirmModalOptions.modalTitle', { - defaultMessage: 'Delete {title}?', + defaultMessage: `Delete '{title}'?`, values: { title: object?.attributes?.title || 'saved Kibana object', }, From 44f59a6909a388abe84e03fc01091630f5eb1ff6 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Tue, 17 Mar 2020 15:07:48 +0100 Subject: [PATCH 10/13] add unit test on view components --- .../__snapshots__/header.test.tsx.snap | 165 ++++++++++ .../__snapshots__/intro.test.tsx.snap | 67 ++++ .../not_found_errors.test.tsx.snap | 301 ++++++++++++++++++ .../components/object_view/field.test.tsx | 87 +++++ .../components/object_view/header.test.tsx | 125 ++++++++ .../objects/components/object_view/header.tsx | 7 +- .../components/object_view/intro.test.tsx | 34 ++ .../object_view/not_found_errors.test.tsx | 64 ++++ 8 files changed, 849 insertions(+), 1 deletion(-) create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/__snapshots__/header.test.tsx.snap create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/__snapshots__/intro.test.tsx.snap create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/__snapshots__/not_found_errors.test.tsx.snap create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/field.test.tsx create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/header.test.tsx create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/intro.test.tsx create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/not_found_errors.test.tsx diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/__snapshots__/header.test.tsx.snap b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/__snapshots__/header.test.tsx.snap new file mode 100644 index 0000000000000..7e1f7ea12b014 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/__snapshots__/header.test.tsx.snap @@ -0,0 +1,165 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Intro component renders correctly 1`] = ` +
+ +
+ +
+ +

+ + Edit search + +

+
+
+
+ +
+ +
+ +
+ + + + + + +
+ + + +
+
+
+ +
+ +
+ +
+`; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/__snapshots__/intro.test.tsx.snap b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/__snapshots__/intro.test.tsx.snap new file mode 100644 index 0000000000000..12abef0c9ff4c --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/__snapshots__/intro.test.tsx.snap @@ -0,0 +1,67 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Intro component renders correctly 1`] = ` + + + } + > +
+
+
+ + +`; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/__snapshots__/not_found_errors.test.tsx.snap b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/__snapshots__/not_found_errors.test.tsx.snap new file mode 100644 index 0000000000000..ac565a000813e --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/__snapshots__/not_found_errors.test.tsx.snap @@ -0,0 +1,301 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`NotFoundErrors component renders correctly for index-pattern type 1`] = ` + + + } + > +
+
+
+ + +`; + +exports[`NotFoundErrors component renders correctly for index-pattern-field type 1`] = ` + + + } + > +
+
+
+ + +`; + +exports[`NotFoundErrors component renders correctly for search type 1`] = ` + + + } + > +
+
+
+ + +`; + +exports[`NotFoundErrors component renders correctly for unknown type 1`] = ` + + + } + > +
+
+