diff --git a/examples/alerting_example/public/alert_types/always_firing.tsx b/examples/alerting_example/public/alert_types/always_firing.tsx index cc74382e3547f..6152aba5ed188 100644 --- a/examples/alerting_example/public/alert_types/always_firing.tsx +++ b/examples/alerting_example/public/alert_types/always_firing.tsx @@ -20,38 +20,16 @@ import React, { Fragment } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiFieldNumber, EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { ALERTING_EXAMPLE_APP_ID } from '../../common/constants'; -import { SanitizedAlert } from '../../../../x-pack/plugins/alerting/common'; -import { PluginSetupContract as AlertingSetup } from '../../../../x-pack/plugins/alerting/public'; -import { - AlertTypeModel, - TriggersAndActionsUIPublicPluginSetup, -} from '../../../../x-pack/plugins/triggers_actions_ui/public'; +import { AlertTypeModel } from '../../../../x-pack/plugins/triggers_actions_ui/public'; import { DEFAULT_INSTANCES_TO_GENERATE } from '../../common/constants'; -export function register( - alerting: AlertingSetup, - triggersActionsUI: TriggersAndActionsUIPublicPluginSetup -) { - registerNavigation(alerting); - triggersActionsUI.alertTypeRegistry.register(getAlertType()); -} - -function registerNavigation(alerting: AlertingSetup) { - alerting.registerNavigation( - ALERTING_EXAMPLE_APP_ID, - 'example.always-firing', - (alert: SanitizedAlert) => `/alert/${alert.id}` - ); -} - interface AlwaysFiringParamsProps { alertParams: { instances?: number }; setAlertParams: (property: string, value: any) => void; errors: { [key: string]: string[] }; } -function getAlertType(): AlertTypeModel { +export function getAlertType(): AlertTypeModel { return { id: 'example.always-firing', name: 'Always Fires', diff --git a/examples/alerting_example/public/alert_types/astros.tsx b/examples/alerting_example/public/alert_types/astros.tsx index 1d8718408d9a6..9bda7da6f140d 100644 --- a/examples/alerting_example/public/alert_types/astros.tsx +++ b/examples/alerting_example/public/alert_types/astros.tsx @@ -34,20 +34,9 @@ import { flatten } from 'lodash'; import { ALERTING_EXAMPLE_APP_ID, Craft, Operator } from '../../common/constants'; import { SanitizedAlert } from '../../../../x-pack/plugins/alerting/common'; import { PluginSetupContract as AlertingSetup } from '../../../../x-pack/plugins/alerting/public'; -import { - AlertTypeModel, - TriggersAndActionsUIPublicPluginSetup, -} from '../../../../x-pack/plugins/triggers_actions_ui/public'; - -export function register( - alerting: AlertingSetup, - triggersActionsUI: TriggersAndActionsUIPublicPluginSetup -) { - registerNavigation(alerting); - triggersActionsUI.alertTypeRegistry.register(getAlertType()); -} +import { AlertTypeModel } from '../../../../x-pack/plugins/triggers_actions_ui/public'; -function registerNavigation(alerting: AlertingSetup) { +export function registerNavigation(alerting: AlertingSetup) { alerting.registerNavigation( ALERTING_EXAMPLE_APP_ID, 'example.people-in-space', @@ -65,7 +54,7 @@ function isValueInEnum(enumeratin: Record, value: any): boolean { return !!Object.values(enumeratin).find(enumVal => enumVal === value); } -function getAlertType(): AlertTypeModel { +export function getAlertType(): AlertTypeModel { return { id: 'example.people-in-space', name: 'People Are In Space Right Now', diff --git a/examples/alerting_example/public/alert_types/index.ts b/examples/alerting_example/public/alert_types/index.ts new file mode 100644 index 0000000000000..96d9c09d15836 --- /dev/null +++ b/examples/alerting_example/public/alert_types/index.ts @@ -0,0 +1,33 @@ +/* + * 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 { registerNavigation as registerPeopleInSpaceNavigation } from './astros'; +import { ALERTING_EXAMPLE_APP_ID } from '../../common/constants'; +import { SanitizedAlert } from '../../../../x-pack/plugins/alerting/common'; +import { PluginSetupContract as AlertingSetup } from '../../../../x-pack/plugins/alerting/public'; + +export function registerNavigation(alerting: AlertingSetup) { + // register default navigation + alerting.registerDefaultNavigation( + ALERTING_EXAMPLE_APP_ID, + (alert: SanitizedAlert) => `/alert/${alert.id}` + ); + + registerPeopleInSpaceNavigation(alerting); +} diff --git a/examples/alerting_example/public/components/view_alert.tsx b/examples/alerting_example/public/components/view_alert.tsx index 9806913130b8e..c1b65eb92edc5 100644 --- a/examples/alerting_example/public/components/view_alert.tsx +++ b/examples/alerting_example/public/components/view_alert.tsx @@ -28,8 +28,6 @@ import { EuiDescriptionListDescription, EuiCodeBlock, EuiSpacer, - EuiFlexGroup, - EuiPanel, } from '@elastic/eui'; import { withRouter, RouteComponentProps } from 'react-router-dom'; import { CoreStart } from 'kibana/public'; diff --git a/examples/alerting_example/public/components/view_astros_alert.tsx b/examples/alerting_example/public/components/view_astros_alert.tsx index e752ee928f04a..db93d8f54924d 100644 --- a/examples/alerting_example/public/components/view_astros_alert.tsx +++ b/examples/alerting_example/public/components/view_astros_alert.tsx @@ -100,14 +100,16 @@ export const ViewPeopleInSpaceAlertPage = withRouter(({ http, id }: Props) => { - {Object.entries(alertState.alertInstances ?? {}).map(([instance, { state }]) => ( - - {instance} - - {hasCraft(state) ? state.craft : 'Unknown Craft'} - - - ))} + {Object.entries(alertState.alertInstances ?? {}).map( + ([instance, { state }], index) => ( + + {instance} + + {hasCraft(state) ? state.craft : 'Unknown Craft'} + + + ) + )} diff --git a/examples/alerting_example/public/plugin.tsx b/examples/alerting_example/public/plugin.tsx index 391c8fef19b7d..299806d393446 100644 --- a/examples/alerting_example/public/plugin.tsx +++ b/examples/alerting_example/public/plugin.tsx @@ -22,8 +22,9 @@ import { PluginSetupContract as AlertingSetup } from '../../../x-pack/plugins/al import { ChartsPluginStart } from '../../../src/plugins/charts/public'; import { TriggersAndActionsUIPublicPluginSetup } from '../../../x-pack/plugins/triggers_actions_ui/public'; import { DataPublicPluginStart } from '../../../src/plugins/data/public'; -import { register as registerAlwaysFiringAlert } from './alert_types/always_firing'; -import { register as registerPeopleInSpaceAlert } from './alert_types/astros'; +import { getAlertType as getAlwaysFiringAlertType } from './alert_types/always_firing'; +import { getAlertType as getPeopleInSpaceAlertType } from './alert_types/astros'; +import { registerNavigation } from './alert_types'; export type Setup = void; export type Start = void; @@ -58,8 +59,10 @@ export class AlertingExamplePlugin implements Plugin; const createAlertNavigationRegistryMock = () => { const mocked: jest.Mocked = { has: jest.fn(), + hasDefaultHandler: jest.fn(), + hasTypedHandler: jest.fn(), register: jest.fn(), + registerDefault: jest.fn(), get: jest.fn(), }; return mocked; diff --git a/x-pack/plugins/alerting/public/mocks.ts b/x-pack/plugins/alerting/public/mocks.ts index 5f928162cadc9..5b99b86c1b7c5 100644 --- a/x-pack/plugins/alerting/public/mocks.ts +++ b/x-pack/plugins/alerting/public/mocks.ts @@ -11,6 +11,7 @@ export type Start = jest.Mocked>; const createSetupContract = (): Setup => ({ registerNavigation: jest.fn(), + registerDefaultNavigation: jest.fn(), }); const createStartContract = (): Start => ({ diff --git a/x-pack/plugins/alerting/public/plugin.ts b/x-pack/plugins/alerting/public/plugin.ts index ccf64be4c3d77..99ed1a9d729fb 100644 --- a/x-pack/plugins/alerting/public/plugin.ts +++ b/x-pack/plugins/alerting/public/plugin.ts @@ -48,6 +48,7 @@ export class AlertingPublicPlugin implements Plugin { const alert = await loadAlert({ http: core.http, alertId }); const alertType = await loadAlertType({ http: core.http, id: alert.alertTypeId }); + if (this.alertNavigationRegistry!.has(alert.consumer, alertType)) { const navigationHandler = this.alertNavigationRegistry!.get(alert.consumer, alertType); const state = navigationHandler(alert, alertType); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/view_in_app.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/view_in_app.tsx index a2ad2a967d7f5..8d70299b8da62 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/view_in_app.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/view_in_app.tsx @@ -40,6 +40,7 @@ export const ViewInApp: React.FunctionComponent = ({ alert }) => return ( { }); }); + describe('View In App', function() { + const testRunUuid = uuid.v4(); + before(async () => { + await pageObjects.common.navigateToApp('triggersActions'); + }); + + it('renders the alert details view in app button', async () => { + const alert = await alerting.alerts.createNoOp(`test-alert-${testRunUuid}`); + + // refresh to see alert + await browser.refresh(); + + await pageObjects.header.waitUntilLoadingHasFinished(); + + // Verify content + await testSubjects.existOrFail('alertsList'); + + // click on first alert + await pageObjects.triggersActionsUI.clickOnAlertInAlertsList(alert.name); + + expect(await pageObjects.alertDetailsUI.isViewInAppEnabled()).to.be(true); + + await pageObjects.alertDetailsUI.clickViewInAppEnabled(); + + expect(await pageObjects.alertDetailsUI.getNoOpAppTitle()).to.be(`View Alert ${alert.id}`); + }); + }); + describe('Alert Instances', function() { const testRunUuid = uuid.v4(); let alert: any; diff --git a/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/public/application.tsx b/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/public/application.tsx new file mode 100644 index 0000000000000..2301a39187801 --- /dev/null +++ b/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/public/application.tsx @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import { BrowserRouter as Router, Route, RouteComponentProps } from 'react-router-dom'; +import { EuiPage, EuiText } from '@elastic/eui'; +import { AppMountParameters, CoreStart } from '../../../../../../../src/core/public'; + +export interface AlertingExampleComponentParams { + basename: string; +} + +const AlertingExampleApp = (deps: AlertingExampleComponentParams) => { + const { basename } = deps; + return ( + + + ) => { + return ( + +

View Alert {props.match.params.id}

+
+ ); + }} + /> +
+
+ ); +}; + +export const renderApp = ( + core: CoreStart, + deps: any, + { appBasePath, element }: AppMountParameters +) => { + ReactDOM.render(, element); + + return () => ReactDOM.unmountComponentAtNode(element); +}; diff --git a/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/public/plugin.ts b/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/public/plugin.ts index 43bc0cc3b0e37..2bf353f79985c 100644 --- a/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/public/plugin.ts +++ b/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/public/plugin.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Plugin, CoreSetup } from 'kibana/public'; +import { Plugin, CoreSetup, AppMountParameters } from 'kibana/public'; import { PluginSetupContract as AlertingSetup } from '../../../../../../plugins/alerting/public'; import { AlertType, SanitizedAlert } from '../../../../../../plugins/alerting/common'; @@ -18,10 +18,20 @@ export interface AlertingExamplePublicSetupDeps { export class AlertingFixturePlugin implements Plugin { public setup(core: CoreSetup, { alerting }: AlertingExamplePublicSetupDeps) { alerting.registerNavigation( - 'consumer.noop', + 'consumer-noop', 'test.noop', (alert: SanitizedAlert, alertType: AlertType) => `/alert/${alert.id}` ); + + core.application.register({ + id: 'consumer-noop', + title: 'No Op App', + async mount(params: AppMountParameters) { + const [coreStart, depsStart] = await core.getStartServices(); + const { renderApp } = await import('./application'); + return renderApp(coreStart, depsStart, params); + }, + }); } public start() {} diff --git a/x-pack/test/functional_with_es_ssl/page_objects/alert_details.ts b/x-pack/test/functional_with_es_ssl/page_objects/alert_details.ts index ddd88cb888534..5ac8d8cb3a974 100644 --- a/x-pack/test/functional_with_es_ssl/page_objects/alert_details.ts +++ b/x-pack/test/functional_with_es_ssl/page_objects/alert_details.ts @@ -102,5 +102,16 @@ export function AlertDetailsPageProvider({ getService }: FtrProviderContext) { const nextButton = await testSubjects.find(`pagination-button-next`); nextButton.click(); }, + async isViewInAppEnabled() { + const viewInAppButton = await testSubjects.find(`alertDetails-viewInApp`); + return (await viewInAppButton.getAttribute('disabled')) !== 'disabled'; + }, + async clickViewInAppEnabled() { + const viewInAppButton = await testSubjects.find(`alertDetails-viewInApp`); + viewInAppButton.click(); + }, + async getNoOpAppTitle() { + return await testSubjects.getVisibleText('noop-title'); + }, }; } diff --git a/x-pack/test/functional_with_es_ssl/services/alerting/alerts.ts b/x-pack/test/functional_with_es_ssl/services/alerting/alerts.ts index 695751cf5ac49..5b506c20e029c 100644 --- a/x-pack/test/functional_with_es_ssl/services/alerting/alerts.ts +++ b/x-pack/test/functional_with_es_ssl/services/alerting/alerts.ts @@ -22,6 +22,31 @@ export class Alerts { }); } + public async createNoOp(name: string) { + this.log.debug(`creating alert ${name}`); + + const { data: alert, status, statusText } = await this.axios.post(`/api/alert`, { + enabled: true, + name, + tags: ['foo'], + alertTypeId: 'test.noop', + consumer: 'consumer-noop', + schedule: { interval: '1m' }, + throttle: '1m', + actions: [], + params: {}, + }); + if (status !== 200) { + throw new Error( + `Expected status code of 200, received ${status} ${statusText}: ${util.inspect(alert)}` + ); + } + + this.log.debug(`created alert ${alert.id}`); + + return alert; + } + public async createAlwaysFiringWithActions( name: string, actions: Array<{