From 27045e09427ee413cd4374b22b15975d714001a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20C=C3=B4t=C3=A9?= Date: Thu, 19 Mar 2020 08:02:07 -0400 Subject: [PATCH 01/55] Make slack param validation handle empty messages (#60468) --- .../actions/server/builtin_action_types/slack.ts | 2 +- .../tests/actions/builtin_action_types/slack.ts | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/actions/server/builtin_action_types/slack.ts b/x-pack/plugins/actions/server/builtin_action_types/slack.ts index 042853796695d..3a351853c1e46 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/slack.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/slack.ts @@ -35,7 +35,7 @@ const SecretsSchema = schema.object(secretsSchemaProps); export type ActionParamsType = TypeOf; const ParamsSchema = schema.object({ - message: schema.string(), + message: schema.string({ minLength: 1 }), }); // action type definition diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/slack.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/slack.ts index 5dcff8712a28d..8afa43bfea21e 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/slack.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/slack.ts @@ -159,6 +159,20 @@ export default function slackTest({ getService }: FtrProviderContext) { expect(result.status).to.eql('ok'); }); + it('should handle an empty message error', async () => { + const { body: result } = await supertest + .post(`/api/action/${simulatedActionId}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: { + message: '', + }, + }) + .expect(200); + expect(result.status).to.eql('error'); + expect(result.message).to.match(/error validating action params: \[message\]: /); + }); + it('should handle a 40x slack error', async () => { const { body: result } = await supertest .post(`/api/action/${simulatedActionId}/_execute`) From 4efeeac560abfda3637e103d38763c860197c6b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20C=C3=B4t=C3=A9?= Date: Thu, 19 Mar 2020 08:06:51 -0400 Subject: [PATCH 02/55] Sort by name when fetching alerts and connectors (#60506) * Sort by name when fetching alerts and connectors * Fix jest tests * Add functional test * Fix failing jest test --- .../plugins/actions/server/mappings.json | 7 +- .../plugins/alerting/server/mappings.json | 7 +- .../actions/server/routes/find.test.ts | 1 + x-pack/plugins/actions/server/routes/find.ts | 2 + .../alerting/server/routes/find.test.ts | 1 + x-pack/plugins/alerting/server/routes/find.ts | 2 + .../lib/action_connector_api.test.ts | 2 + .../application/lib/action_connector_api.ts | 2 + .../public/application/lib/alert_api.test.ts | 152 ++++++++++-------- .../public/application/lib/alert_api.ts | 2 + .../apps/triggers_actions_ui/alerts.ts | 49 ++++-- 11 files changed, 141 insertions(+), 86 deletions(-) diff --git a/x-pack/legacy/plugins/actions/server/mappings.json b/x-pack/legacy/plugins/actions/server/mappings.json index a9c4d80b00af1..ef6a0c9919920 100644 --- a/x-pack/legacy/plugins/actions/server/mappings.json +++ b/x-pack/legacy/plugins/actions/server/mappings.json @@ -2,7 +2,12 @@ "action": { "properties": { "name": { - "type": "text" + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } }, "actionTypeId": { "type": "keyword" diff --git a/x-pack/legacy/plugins/alerting/server/mappings.json b/x-pack/legacy/plugins/alerting/server/mappings.json index 31733f44e7ce6..a7e85febf2446 100644 --- a/x-pack/legacy/plugins/alerting/server/mappings.json +++ b/x-pack/legacy/plugins/alerting/server/mappings.json @@ -5,7 +5,12 @@ "type": "boolean" }, "name": { - "type": "text" + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } }, "tags": { "type": "keyword" diff --git a/x-pack/plugins/actions/server/routes/find.test.ts b/x-pack/plugins/actions/server/routes/find.test.ts index 862e26132fdc3..b51130b2640aa 100644 --- a/x-pack/plugins/actions/server/routes/find.test.ts +++ b/x-pack/plugins/actions/server/routes/find.test.ts @@ -81,6 +81,7 @@ describe('findActionRoute', () => { "perPage": 1, "search": undefined, "sortField": undefined, + "sortOrder": undefined, }, }, ] diff --git a/x-pack/plugins/actions/server/routes/find.ts b/x-pack/plugins/actions/server/routes/find.ts index 71d4274980fcc..820dd32d710ae 100644 --- a/x-pack/plugins/actions/server/routes/find.ts +++ b/x-pack/plugins/actions/server/routes/find.ts @@ -26,6 +26,7 @@ const querySchema = schema.object({ }), search_fields: schema.maybe(schema.oneOf([schema.arrayOf(schema.string()), schema.string()])), sort_field: schema.maybe(schema.string()), + sort_order: schema.maybe(schema.oneOf([schema.literal('asc'), schema.literal('desc')])), has_reference: schema.maybe( // use nullable as maybe is currently broken // in config-schema @@ -70,6 +71,7 @@ export const findActionRoute = (router: IRouter, licenseState: LicenseState) => sortField: query.sort_field, fields: query.fields, filter: query.filter, + sortOrder: query.sort_order, }; if (query.search_fields) { diff --git a/x-pack/plugins/alerting/server/routes/find.test.ts b/x-pack/plugins/alerting/server/routes/find.test.ts index ba0114c99a9bd..391d6df3f9931 100644 --- a/x-pack/plugins/alerting/server/routes/find.test.ts +++ b/x-pack/plugins/alerting/server/routes/find.test.ts @@ -82,6 +82,7 @@ describe('findAlertRoute', () => { "perPage": 1, "search": undefined, "sortField": undefined, + "sortOrder": undefined, }, }, ] diff --git a/x-pack/plugins/alerting/server/routes/find.ts b/x-pack/plugins/alerting/server/routes/find.ts index efc5c3ea97183..1f8f161cf3028 100644 --- a/x-pack/plugins/alerting/server/routes/find.ts +++ b/x-pack/plugins/alerting/server/routes/find.ts @@ -26,6 +26,7 @@ const querySchema = schema.object({ }), search_fields: schema.maybe(schema.oneOf([schema.arrayOf(schema.string()), schema.string()])), sort_field: schema.maybe(schema.string()), + sort_order: schema.maybe(schema.oneOf([schema.literal('asc'), schema.literal('desc')])), has_reference: schema.maybe( // use nullable as maybe is currently broken // in config-schema @@ -70,6 +71,7 @@ export const findAlertRoute = (router: IRouter, licenseState: LicenseState) => { sortField: query.sort_field, fields: query.fields, filter: query.filter, + sortOrder: query.sort_order, }; if (query.search_fields) { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.test.ts index f568e0a71d0cf..62e7b1cf022bb 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.test.ts @@ -57,6 +57,8 @@ describe('loadAllActions', () => { Object { "query": Object { "per_page": 10000, + "sort_field": "name.keyword", + "sort_order": "asc", }, }, ] diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.ts index 5b2b59603d281..26ad97f05849d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.ts @@ -30,6 +30,8 @@ export async function loadAllActions({ return await http.get(`${BASE_ACTION_API_PATH}/_find`, { query: { per_page: MAX_ACTIONS_RETURNED, + sort_field: 'name.keyword', + sort_order: 'asc', }, }); } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts index 0b06982828446..0555823d0245e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts @@ -170,6 +170,8 @@ describe('loadAlerts', () => { "per_page": 10, "search": undefined, "search_fields": undefined, + "sort_field": "name.keyword", + "sort_order": "asc", }, }, ] @@ -188,20 +190,22 @@ describe('loadAlerts', () => { const result = await loadAlerts({ http, searchText: 'apples', page: { index: 0, size: 10 } }); expect(result).toEqual(resolvedValue); expect(http.get.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "/api/alert/_find", - Object { - "query": Object { - "default_search_operator": "AND", - "filter": undefined, - "page": 1, - "per_page": 10, - "search": "apples", - "search_fields": "[\\"name\\",\\"tags\\"]", - }, - }, - ] - `); + Array [ + "/api/alert/_find", + Object { + "query": Object { + "default_search_operator": "AND", + "filter": undefined, + "page": 1, + "per_page": 10, + "search": "apples", + "search_fields": "[\\"name\\",\\"tags\\"]", + "sort_field": "name.keyword", + "sort_order": "asc", + }, + }, + ] + `); }); test('should call find API with actionTypesFilter', async () => { @@ -220,20 +224,22 @@ describe('loadAlerts', () => { }); expect(result).toEqual(resolvedValue); expect(http.get.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "/api/alert/_find", - Object { - "query": Object { - "default_search_operator": "AND", - "filter": undefined, - "page": 1, - "per_page": 10, - "search": "foo", - "search_fields": "[\\"name\\",\\"tags\\"]", - }, - }, - ] - `); + Array [ + "/api/alert/_find", + Object { + "query": Object { + "default_search_operator": "AND", + "filter": undefined, + "page": 1, + "per_page": 10, + "search": "foo", + "search_fields": "[\\"name\\",\\"tags\\"]", + "sort_field": "name.keyword", + "sort_order": "asc", + }, + }, + ] + `); }); test('should call find API with typesFilter', async () => { @@ -252,20 +258,22 @@ describe('loadAlerts', () => { }); expect(result).toEqual(resolvedValue); expect(http.get.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "/api/alert/_find", - Object { - "query": Object { - "default_search_operator": "AND", - "filter": "alert.attributes.alertTypeId:(foo or bar)", - "page": 1, - "per_page": 10, - "search": undefined, - "search_fields": undefined, - }, - }, - ] - `); + Array [ + "/api/alert/_find", + Object { + "query": Object { + "default_search_operator": "AND", + "filter": "alert.attributes.alertTypeId:(foo or bar)", + "page": 1, + "per_page": 10, + "search": undefined, + "search_fields": undefined, + "sort_field": "name.keyword", + "sort_order": "asc", + }, + }, + ] + `); }); test('should call find API with actionTypesFilter and typesFilter', async () => { @@ -285,20 +293,22 @@ describe('loadAlerts', () => { }); expect(result).toEqual(resolvedValue); expect(http.get.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "/api/alert/_find", - Object { - "query": Object { - "default_search_operator": "AND", - "filter": "alert.attributes.alertTypeId:(foo or bar)", - "page": 1, - "per_page": 10, - "search": "baz", - "search_fields": "[\\"name\\",\\"tags\\"]", - }, - }, - ] - `); + Array [ + "/api/alert/_find", + Object { + "query": Object { + "default_search_operator": "AND", + "filter": "alert.attributes.alertTypeId:(foo or bar)", + "page": 1, + "per_page": 10, + "search": "baz", + "search_fields": "[\\"name\\",\\"tags\\"]", + "sort_field": "name.keyword", + "sort_order": "asc", + }, + }, + ] + `); }); test('should call find API with searchText and tagsFilter and typesFilter', async () => { @@ -318,20 +328,22 @@ describe('loadAlerts', () => { }); expect(result).toEqual(resolvedValue); expect(http.get.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "/api/alert/_find", - Object { - "query": Object { - "default_search_operator": "AND", - "filter": "alert.attributes.alertTypeId:(foo or bar)", - "page": 1, - "per_page": 10, - "search": "apples, foo, baz", - "search_fields": "[\\"name\\",\\"tags\\"]", - }, - }, - ] - `); + Array [ + "/api/alert/_find", + Object { + "query": Object { + "default_search_operator": "AND", + "filter": "alert.attributes.alertTypeId:(foo or bar)", + "page": 1, + "per_page": 10, + "search": "apples, foo, baz", + "search_fields": "[\\"name\\",\\"tags\\"]", + "sort_field": "name.keyword", + "sort_order": "asc", + }, + }, + ] + `); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.ts index ff6b4ba17c6d9..1b18460ba11cb 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.ts @@ -87,6 +87,8 @@ export async function loadAlerts({ search: searchText, filter: filters.length ? filters.join(' and ') : undefined, default_search_operator: 'AND', + sort_field: 'name.keyword', + sort_order: 'asc', }, }); } diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts index 791712fa24489..4354b19da24ac 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts @@ -18,20 +18,21 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const supertest = getService('supertest'); const find = getService('find'); - async function createAlert(alertTypeId?: string, name?: string, params?: any) { + async function createAlert(overwrites: Record = {}) { const { body: createdAlert } = await supertest .post(`/api/alert`) .set('kbn-xsrf', 'foo') .send({ enabled: true, - name: name ?? generateUniqueKey(), + name: generateUniqueKey(), tags: ['foo', 'bar'], - alertTypeId: alertTypeId ?? 'test.noop', + alertTypeId: 'test.noop', consumer: 'test', schedule: { interval: '1m' }, throttle: '1m', actions: [], - params: params ?? {}, + params: {}, + ...overwrites, }) .expect(200); return createdAlert; @@ -98,6 +99,22 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { ]); }); + it('should display alerts in alphabetical order', async () => { + const uniqueKey = generateUniqueKey(); + await createAlert({ name: 'b', tags: [uniqueKey] }); + await createAlert({ name: 'c', tags: [uniqueKey] }); + await createAlert({ name: 'a', tags: [uniqueKey] }); + + await pageObjects.common.navigateToApp('triggersActions'); + await pageObjects.triggersActionsUI.searchAlerts(uniqueKey); + + const searchResults = await pageObjects.triggersActionsUI.getAlertsList(); + expect(searchResults).to.have.length(3); + expect(searchResults[0].name).to.eql('a'); + expect(searchResults[1].name).to.eql('b'); + expect(searchResults[2].name).to.eql('c'); + }); + it('should search for alert', async () => { const createdAlert = await createAlert(); await pageObjects.common.navigateToApp('triggersActions'); @@ -115,16 +132,20 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); it('should edit an alert', async () => { - const createdAlert = await createAlert('.index-threshold', 'new alert', { - aggType: 'count', - termSize: 5, - thresholdComparator: '>', - timeWindowSize: 5, - timeWindowUnit: 'm', - groupBy: 'all', - threshold: [1000, 5000], - index: ['.kibana_1'], - timeField: 'alert', + const createdAlert = await createAlert({ + alertTypeId: '.index-threshold', + name: 'new alert', + params: { + aggType: 'count', + termSize: 5, + thresholdComparator: '>', + timeWindowSize: 5, + timeWindowUnit: 'm', + groupBy: 'all', + threshold: [1000, 5000], + index: ['.kibana_1'], + timeField: 'alert', + }, }); await pageObjects.common.navigateToApp('triggersActions'); await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); From ee6bb64f1330fd8539a642affdf00db15fc2ff56 Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Thu, 19 Mar 2020 08:23:20 -0400 Subject: [PATCH 03/55] [Remote clusters] Update copy (#60382) --- .../remote_cluster_form.test.js.snap | 196 ++++++------------ .../remote_cluster_form.js | 51 ++--- .../remote_cluster_page_title.js | 24 ++- .../remote_cluster_add/remote_cluster_add.js | 6 + .../remote_cluster_edit.js | 2 +- .../detail_panel/detail_panel.js | 60 +++--- .../remote_cluster_table.js | 2 +- .../application/services/documentation.ts | 2 + 8 files changed, 152 insertions(+), 191 deletions(-) diff --git a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap index 88b869b1d1d8f..b5bf4057f0e36 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap +++ b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap @@ -126,7 +126,7 @@ exports[`RemoteClusterForm proxy mode renders correct connection settings when u @@ -209,11 +209,11 @@ exports[`RemoteClusterForm proxy mode renders correct connection settings when u className="euiTextColor euiTextColor--subdued" > - A unique name for the remote cluster. + A unique name for the cluster. @@ -364,7 +364,7 @@ exports[`RemoteClusterForm proxy mode renders correct connection settings when u description={ @@ -374,25 +374,6 @@ exports[`RemoteClusterForm proxy mode renders correct connection settings when u fullWidth={true} hasChildLabel={true} hasEmptyLabelSpace={true} - helpText={ - - - , - } - } - /> - } labelType="label" > @@ -488,11 +469,11 @@ exports[`RemoteClusterForm proxy mode renders correct connection settings when u className="euiTextColor euiTextColor--subdued" > - Remote cluster connections work by configuring a remote cluster and connecting only to a limited number of nodes in that remote cluster. + Use seed nodes by default, or switch to a single proxy address. - - , - } - } - /> - } labelType="label" >
@@ -549,7 +510,6 @@ exports[`RemoteClusterForm proxy mode renders correct connection settings when u >
- -
- - - , - } - } - > - Configure a remote cluster with a single proxy address. - - - - -
-
@@ -685,7 +600,7 @@ exports[`RemoteClusterForm proxy mode renders correct connection settings when u hasEmptyLabelSpace={false} helpText={ @@ -786,11 +701,11 @@ exports[`RemoteClusterForm proxy mode renders correct connection settings when u id="mockId-help" > - The address used for all remote connections. + The address to use for remote connections. @@ -920,14 +835,26 @@ exports[`RemoteClusterForm proxy mode renders correct connection settings when u hasEmptyLabelSpace={false} helpText={ + + , + } + } /> } label={ @@ -953,11 +880,11 @@ exports[`RemoteClusterForm proxy mode renders correct connection settings when u htmlFor="mockId" > - Server name + Server name (optional) @@ -1010,11 +937,39 @@ exports[`RemoteClusterForm proxy mode renders correct connection settings when u id="mockId-help" > + + , + } + } > - An optional hostname string which will be sent in the server_name field of the TLS Server Name Indication extension if TLS is enabled. + A string sent in the server_name field of the TLS Server Name Indication extension if TLS is enabled. + + + @@ -1033,7 +988,7 @@ exports[`RemoteClusterForm proxy mode renders correct connection settings when u

- By default, a request fails if any of the queried remote clusters are unavailable. To continue sending a request to other remote clusters if this cluster is unavailable, enable + A request fails if any of the queried remote clusters are unavailable. To send requests to other remote clusters if this cluster is unavailable, enable - A unique name for the remote cluster. + A unique name for the cluster. @@ -1534,7 +1489,7 @@ Array [

- Remote cluster connections work by configuring a remote cluster and connecting only to a limited number of nodes in that remote cluster. + Use seed nodes by default, or switch to a single proxy address.
-
- Configure a remote cluster with a single proxy address. - -
@@ -1717,7 +1659,7 @@ Array [ class="euiFormHelpText euiFormRow__text" id="mockId-help" > - The number of gateway nodes to connect to. + The number of gateway nodes to connect to for this cluster. @@ -1751,7 +1693,7 @@ Array [ class="euiTextColor euiTextColor--subdued" >

- By default, a request fails if any of the queried remote clusters are unavailable. To continue sending a request to other remote clusters if this cluster is unavailable, enable + A request fails if any of the queried remote clusters are unavailable. To send requests to other remote clusters if this cluster is unavailable, enable Skip if unavailable diff --git a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/remote_cluster_form.js b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/remote_cluster_form.js index 358ffc03da783..94d6ca4ebb648 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/remote_cluster_form.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/remote_cluster_form.js @@ -37,7 +37,7 @@ import { import { skippingDisconnectedClustersUrl, transportPortUrl, - proxyModeUrl, + proxySettingsUrl, } from '../../../services/documentation'; import { RequestFlyout } from './request_flyout'; @@ -328,7 +328,7 @@ export class RemoteClusterForm extends Component { helpText={ } fullWidth @@ -363,7 +363,7 @@ export class RemoteClusterForm extends Component { helpText={ } isInvalid={Boolean(areErrorsVisible && errorProxyAddress)} @@ -414,13 +414,23 @@ export class RemoteClusterForm extends Component { label={ } helpText={ + + + ), + }} /> } fullWidth @@ -456,33 +466,14 @@ export class RemoteClusterForm extends Component { <> - - - - ), - }} - /> - } - > + } checked={mode === PROXY_MODE} @@ -523,9 +514,7 @@ export class RemoteClusterForm extends Component {

@@ -839,7 +828,7 @@ export class RemoteClusterForm extends Component { description={ } fullWidth diff --git a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_page_title/remote_cluster_page_title.js b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_page_title/remote_cluster_page_title.js index 82d8a7b0cfa8b..5a3b1faedad2b 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_page_title/remote_cluster_page_title.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_page_title/remote_cluster_page_title.js @@ -7,23 +7,22 @@ import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage } from '@kbn/i18n/react'; +import { remoteClustersUrl } from '../../../services/documentation'; import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, - EuiPageContentHeader, EuiSpacer, + EuiText, EuiTitle, } from '@elastic/eui'; -import { remoteClustersUrl } from '../../../services/documentation'; - -export const RemoteClusterPageTitle = ({ title }) => ( +export const RemoteClusterPageTitle = ({ title, description }) => ( - + @@ -47,10 +46,23 @@ export const RemoteClusterPageTitle = ({ title }) => ( - + + + {description ? ( + <> + + + + {description} + + + ) : null} + + ); RemoteClusterPageTitle.propTypes = { title: PropTypes.node.isRequired, + description: PropTypes.node, }; diff --git a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_add/remote_cluster_add.js b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_add/remote_cluster_add.js index 4a861695c0eb3..0531310bd097b 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_add/remote_cluster_add.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_add/remote_cluster_add.js @@ -65,6 +65,12 @@ export class RemoteClusterAdd extends PureComponent { defaultMessage="Add remote cluster" /> } + description={ + + } /> diff --git a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/detail_panel/detail_panel.js b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/detail_panel/detail_panel.js index 89a48927f6833..4006422d3df50 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/detail_panel/detail_panel.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/detail_panel/detail_panel.js @@ -125,7 +125,7 @@ export class DetailPanel extends Component { title={ - - - - ) : ( - - - - ), - }} - /> + {/* A remote cluster is not editable if configured in elasticsearch.yml, so we direct the user to documentation instead */} + {isConfiguredByNode ? ( + + + + ), + }} + /> + ) : ( + + + + ), + }} + /> + )} @@ -249,7 +259,7 @@ export class DetailPanel extends Component { @@ -363,7 +373,7 @@ export class DetailPanel extends Component { diff --git a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_table/remote_cluster_table.js b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_table/remote_cluster_table.js index ec20805ccd919..73f32fe8bca5b 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_table/remote_cluster_table.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_table/remote_cluster_table.js @@ -141,7 +141,7 @@ export class RemoteClusterTable extends Component { content={ } /> diff --git a/x-pack/plugins/remote_clusters/public/application/services/documentation.ts b/x-pack/plugins/remote_clusters/public/application/services/documentation.ts index f6f5dc987c2eb..76744e90096da 100644 --- a/x-pack/plugins/remote_clusters/public/application/services/documentation.ts +++ b/x-pack/plugins/remote_clusters/public/application/services/documentation.ts @@ -10,6 +10,7 @@ export let skippingDisconnectedClustersUrl: string; export let remoteClustersUrl: string; export let transportPortUrl: string; export let proxyModeUrl: string; +export let proxySettingsUrl: string; export function init(docLinks: DocLinksStart): void { const { DOC_LINK_VERSION, ELASTIC_WEBSITE_URL } = docLinks; @@ -19,4 +20,5 @@ export function init(docLinks: DocLinksStart): void { remoteClustersUrl = `${esDocBasePath}/modules-remote-clusters.html`; transportPortUrl = `${esDocBasePath}/modules-transport.html`; proxyModeUrl = `${esDocBasePath}/modules-remote-clusters.html#proxy-mode`; + proxySettingsUrl = `${esDocBasePath}/modules-remote-clusters.html#remote-cluster-proxy-settings`; } From 6ed2918b6c76077af87ed0941d1cb457992a35e8 Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Thu, 19 Mar 2020 15:06:33 +0200 Subject: [PATCH 04/55] [SIEM][CASE] Configuration page action bar (#60608) * Add bottom bar * Add listeners --- .../case/components/configure_cases/index.tsx | 97 ++++++++++++------- 1 file changed, 62 insertions(+), 35 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.tsx index b3c424bef6a7a..cbc3be6d144a2 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.tsx @@ -7,8 +7,15 @@ import React, { useReducer, useCallback, useEffect, useState } from 'react'; import styled, { css } from 'styled-components'; -import { EuiFlexGroup, EuiFlexItem, EuiButton, EuiSpacer, EuiCallOut } from '@elastic/eui'; -import { noop, isEmpty } from 'lodash/fp'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiCallOut, + EuiBottomBar, + EuiButtonEmpty, +} from '@elastic/eui'; +import { isEmpty } from 'lodash/fp'; import { useKibana } from '../../../../lib/kibana'; import { useConnectors } from '../../../../containers/case/configure/use_connectors'; import { useCaseConfigure } from '../../../../containers/case/configure/use_configure'; @@ -32,6 +39,9 @@ import { Mapping } from '../configure_cases/mapping'; import { SectionWrapper } from '../wrappers'; import { configureCasesReducer, State } from './reducer'; import * as i18n from './translations'; +import { getCaseUrl } from '../../../../components/link_to'; + +const CASE_URL = getCaseUrl(); const FormWrapper = styled.div` ${({ theme }) => css` @@ -68,6 +78,8 @@ const ConfigureCasesComponent: React.FC = () => { null ); + const [actionBarVisible, setActionBarVisible] = useState(false); + const handleShowAddFlyout = useCallback(() => setAddFlyoutVisibility(true), []); const [{ connectorId, closureType, mapping }, dispatch] = useReducer( @@ -111,11 +123,22 @@ const ConfigureCasesComponent: React.FC = () => { const handleSubmit = useCallback( // TO DO give a warning/error to user when field are not mapped so they have chance to do it () => { + setActionBarVisible(false); persistCaseConfigure({ connectorId, closureType }); }, [connectorId, closureType, mapping] ); + const onChangeConnector = useCallback((newConnectorId: string) => { + setActionBarVisible(true); + setConnectorId(newConnectorId); + }, []); + + const onChangeClosureType = useCallback((newClosureType: ClosureType) => { + setActionBarVisible(true); + setClosureType(newClosureType); + }, []); + useEffect(() => { if ( !isEmpty(connectors) && @@ -171,7 +194,7 @@ const ConfigureCasesComponent: React.FC = () => { connectors={connectors ?? []} disabled={persistLoading || isLoadingConnectors} isLoading={isLoadingConnectors} - onChangeConnector={setConnectorId} + onChangeConnector={onChangeConnector} handleShowAddFlyout={handleShowAddFlyout} selectedConnector={connectorId} /> @@ -180,7 +203,7 @@ const ConfigureCasesComponent: React.FC = () => { @@ -192,37 +215,41 @@ const ConfigureCasesComponent: React.FC = () => { setEditFlyoutVisibility={setEditFlyoutVisibility} /> - - - - - - {i18n.CANCEL} - - - - - {i18n.SAVE_CHANGES} - - - - + {actionBarVisible && ( + + + + + + + {i18n.CANCEL} + + + + + {i18n.SAVE_CHANGES} + + + + + + + )} Date: Thu, 19 Mar 2020 14:09:44 +0100 Subject: [PATCH 05/55] migrate saved objects management edition view to react/typescript/eui (#59490) * migrate so management edition view to react * fix bundle name + add forgotten data-test-subj * add FTR tests for edition page * EUIfy react components * wrap form with EuiPanel + caps btns labels * Wrapping whole view in page content panel and removing legacy classes * improve delete confirmation modal * update translations * improve delete popin * add unit test on view components * remove kui classes & address comments * extract createFieldList and add tests * disable form submit during submition Co-authored-by: cchaos --- .../public/overlays/modal/modal_service.tsx | 1 + .../management/saved_object_registry.ts | 8 +- .../management/sections/objects/_objects.js | 1 - .../management/sections/objects/_view.html | 204 +------- .../management/sections/objects/_view.js | 309 ++---------- .../__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 | 95 ++++ .../objects/components/object_view/field.tsx | 162 +++++++ .../objects/components/object_view/form.tsx | 186 +++++++ .../components/object_view/header.test.tsx | 125 +++++ .../objects/components/object_view/header.tsx | 110 +++++ .../objects/components/object_view/index.ts | 23 + .../components/object_view/intro.test.tsx | 34 ++ .../objects/components/object_view/intro.tsx | 44 ++ .../object_view/not_found_errors.test.tsx | 64 +++ .../object_view/not_found_errors.tsx | 77 +++ .../objects/lib/create_field_list.test.ts | 132 +++++ .../sections/objects/lib/create_field_list.ts | 135 ++++++ .../lib/{in_app_url.js => in_app_url.ts} | 14 +- .../sections/objects/saved_object_view.tsx | 176 +++++++ .../management/sections/objects/types.ts | 38 ++ .../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 ++++++++++++++++++ .../translations/translations/ja-JP.json | 2 +- .../translations/translations/zh-CN.json | 2 +- 30 files changed, 2675 insertions(+), 475 deletions(-) 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/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.test.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.test.tsx 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.test.tsx create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/objects/components/object_view/not_found_errors.tsx create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/objects/lib/create_field_list.test.ts create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/objects/lib/create_field_list.ts 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 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/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/saved_object_registry.ts b/src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts index 8e73a09480c41..cb9ac0e01bb7f 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..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,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/__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..812031b4b363c --- /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`] = ` + + + } + > +
+
+