diff --git a/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts b/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts
index b2e5f6257669c..109bb7df8cc18 100644
--- a/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts
+++ b/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts
@@ -63,7 +63,8 @@ afterAll(async () => {
await del(TMP_DIR);
});
-it('builds expected bundles, saves bundle counts to metadata', async () => {
+// FLAKY: https://github.com/elastic/kibana/issues/70762
+it.skip('builds expected bundles, saves bundle counts to metadata', async () => {
const config = OptimizerConfig.create({
repoRoot: MOCK_REPO_DIR,
pluginScanDirs: [Path.resolve(MOCK_REPO_DIR, 'plugins')],
@@ -167,7 +168,8 @@ it('builds expected bundles, saves bundle counts to metadata', async () => {
`);
});
-it('uses cache on second run and exist cleanly', async () => {
+// FLAKY: https://github.com/elastic/kibana/issues/70764
+it.skip('uses cache on second run and exist cleanly', async () => {
const config = OptimizerConfig.create({
repoRoot: MOCK_REPO_DIR,
pluginScanDirs: [Path.resolve(MOCK_REPO_DIR, 'plugins')],
diff --git a/test/functional/apps/dashboard/index.js b/test/functional/apps/dashboard/index.js
index 1e310c1ddd268..5a30456bd59ab 100644
--- a/test/functional/apps/dashboard/index.js
+++ b/test/functional/apps/dashboard/index.js
@@ -49,6 +49,7 @@ export default function ({ getService, loadTestFile }) {
after(unloadCurrentData);
loadTestFile(require.resolve('./empty_dashboard'));
+ loadTestFile(require.resolve('./url_field_formatter'));
loadTestFile(require.resolve('./embeddable_rendering'));
loadTestFile(require.resolve('./create_and_add_embeddables'));
loadTestFile(require.resolve('./edit_embeddable_redirects'));
diff --git a/test/functional/apps/dashboard/url_field_formatter.ts b/test/functional/apps/dashboard/url_field_formatter.ts
new file mode 100644
index 0000000000000..9b05b9b777b94
--- /dev/null
+++ b/test/functional/apps/dashboard/url_field_formatter.ts
@@ -0,0 +1,91 @@
+/*
+ * 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 { WebElementWrapper } from 'test/functional/services/lib/web_element_wrapper';
+import { FtrProviderContext } from '../../ftr_provider_context';
+
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
+ const { common, dashboard, settings, timePicker, visChart } = getPageObjects([
+ 'common',
+ 'dashboard',
+ 'settings',
+ 'timePicker',
+ 'visChart',
+ ]);
+ const esArchiver = getService('esArchiver');
+ const kibanaServer = getService('kibanaServer');
+ const testSubjects = getService('testSubjects');
+ const browser = getService('browser');
+ const fieldName = 'clientip';
+
+ const clickFieldAndCheckUrl = async (fieldLink: WebElementWrapper) => {
+ const fieldValue = await fieldLink.getVisibleText();
+ await fieldLink.click();
+ const windowHandlers = await browser.getAllWindowHandles();
+ expect(windowHandlers.length).to.equal(2);
+ await browser.switchToWindow(windowHandlers[1]);
+ const currentUrl = await browser.getCurrentUrl();
+ const fieldUrl = common.getHostPort() + '/app/' + fieldValue;
+ expect(currentUrl).to.equal(fieldUrl);
+ };
+
+ describe('Changing field formatter to Url', () => {
+ before(async function () {
+ await esArchiver.load('dashboard/current/kibana');
+ await kibanaServer.uiSettings.replace({
+ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c',
+ });
+ await common.navigateToApp('settings');
+ await settings.clickKibanaIndexPatterns();
+ await settings.clickIndexPatternLogstash();
+ await settings.filterField(fieldName);
+ await settings.openControlsByName(fieldName);
+ await settings.setFieldFormat('url');
+ await settings.controlChangeSave();
+ });
+
+ it('applied on dashboard', async () => {
+ await common.navigateToApp('dashboard');
+ await dashboard.loadSavedDashboard('dashboard with everything');
+ await dashboard.waitForRenderComplete();
+ const fieldLink = await visChart.getFieldLinkInVisTable(`${fieldName}: Descending`, 1);
+ await clickFieldAndCheckUrl(fieldLink);
+ });
+
+ it('applied on discover', async () => {
+ await common.navigateToApp('discover');
+ await timePicker.setAbsoluteRange(
+ 'Sep 19, 2017 @ 06:31:44.000',
+ 'Sep 23, 2018 @ 18:31:44.000'
+ );
+ await testSubjects.click('docTableExpandToggleColumn');
+ const fieldLink = await testSubjects.find(`tableDocViewRow-${fieldName}-value`);
+ await clickFieldAndCheckUrl(fieldLink);
+ });
+
+ afterEach(async function () {
+ const windowHandlers = await browser.getAllWindowHandles();
+ if (windowHandlers.length > 1) {
+ await browser.closeCurrentWindow();
+ await browser.switchToWindow(windowHandlers[0]);
+ }
+ });
+ });
+}
diff --git a/test/functional/page_objects/visualize_chart_page.ts b/test/functional/page_objects/visualize_chart_page.ts
index 673fba0c346b8..590631ad48b00 100644
--- a/test/functional/page_objects/visualize_chart_page.ts
+++ b/test/functional/page_objects/visualize_chart_page.ts
@@ -302,6 +302,20 @@ export function VisualizeChartPageProvider({ getService, getPageObjects }: FtrPr
return element.getVisibleText();
}
+ public async getFieldLinkInVisTable(fieldName: string, rowIndex: number = 1) {
+ const tableVis = await testSubjects.find('tableVis');
+ const $ = await tableVis.parseDomContent();
+ const headers = $('span[ng-bind="::col.title"]')
+ .toArray()
+ .map((header: any) => $(header).text());
+ const fieldColumnIndex = headers.indexOf(fieldName);
+ return await find.byCssSelector(
+ `[data-test-subj="paginated-table-body"] tr:nth-of-type(${rowIndex}) td:nth-of-type(${
+ fieldColumnIndex + 1
+ }) a`
+ );
+ }
+
/**
* If you are writing new tests, you should rather look into getTableVisContent method instead.
* @deprecated Use getTableVisContent instead.
diff --git a/x-pack/plugins/actions/README.md b/x-pack/plugins/actions/README.md
index 605676cee363d..494f2f38e8bff 100644
--- a/x-pack/plugins/actions/README.md
+++ b/x-pack/plugins/actions/README.md
@@ -71,6 +71,7 @@ Table of Contents
- [`params`](#params-7)
- [`subActionParams (pushToService)`](#subactionparams-pushtoservice-1)
- [Command Line Utility](#command-line-utility)
+- [Developing New Action Types](#developing-new-action-types)
## Terminology
@@ -606,3 +607,39 @@ $ kbn-action create .slack "post to slack" '{"webhookUrl": "https://hooks.slack.
"version": "WzMsMV0="
}
```
+
+# Developing New Action Types
+
+When creating a new action type, your plugin will eventually call `server.plugins.actions.setup.registerType()` to register the type with the actions plugin, but there are some additional things to think about about and implement.
+
+Consider working with the alerting team on early structure /design feedback of new actions, especially as the APIs and infrastructure are still under development.
+
+## licensing
+
+Currently actions are licensed as "basic" if the action only interacts with the stack, eg the server log and es index actions. Other actions are at least "gold" level.
+
+## plugin location
+
+Currently actions that are licensed as "basic" **MUST** be implemented in the actions plugin, other actions can be implemented in any other plugin that pre-reqs the actions plugin. If the new action is generic across the stack, it probably belongs in the actions plugin, but if your action is very specific to a plugin/solution, it might be easiest to implement it in the plugin/solution. Keep in mind that if Kibana is run without the plugin being enabled, any actions defined in that plugin will not run, nor will those actions be available via APIs or UI.
+
+Actions that take URLs or hostnames should check that those values are whitelisted. The whitelisting utilities are currently internal to the actions plugin, and so such actions will need to be implemented in the actions plugin. Longer-term, we will expose these utilities so they can be used by alerts implemented in other plugins; see [issue #64659](https://github.com/elastic/kibana/issues/64659).
+
+## documentation
+
+You should also create some asciidoc for the new action type. An entry should be made in the action type index - [`docs/user/alerting/action-types.asciidoc`](../../../docs/user/alerting/action-types.asciidoc) which points to a new document for the action type that should be in the directory [`docs/user/alerting/action-types`](../../../docs/user/alerting/action-types).
+
+## tests
+
+The action type should have both jest tests and functional tests. For functional tests, if your action interacts with a 3rd party service via HTTP, you may be able to create a simulator for your service, to test with. See the existing functional test servers in the directory [`x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server`](../../test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server)
+
+## action type config and secrets
+
+Action types must define `config` and `secrets` which are used to create connectors. This data should be described with `@kbn/config-schema` object schemas, and you **MUST NOT** use `schema.maybe()` to define properties.
+
+This is due to the fact that the structures are persisted in saved objects, which performs partial updates on the persisted data. If a property value is already persisted, but an update either doesn't include the property, or sets it to `undefined`, the persisted value will not be changed. Beyond this being a semantic error in general, it also ends up invalidating the encryption used to save secrets, and will render the secrets will not be able to be unencrypted later.
+
+Instead of `schema.maybe()`, use `schema.nullable()`, which is the same as `schema.maybe()` except that when passed an `undefined` value, the object returned from the validation will be set to `null`. The resulting type will be `property-type | null`, whereas with `schema.maybe()` it would be `property-type | undefined`.
+
+## user interface
+
+In order to make this action usable in the Kibana UI, you will need to provide all the UI editing aspects of the action. The existing action type user interfaces are defined in [`x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types`](../triggers_actions_ui/public/application/components/builtin_action_types). For more information, see the [UI documentation](../triggers_actions_ui/README.md#create-and-register-new-action-type-ui).
diff --git a/x-pack/plugins/apm/common/agent_name.ts b/x-pack/plugins/apm/common/agent_name.ts
index 630f5739806af..9d462dad87ec0 100644
--- a/x-pack/plugins/apm/common/agent_name.ts
+++ b/x-pack/plugins/apm/common/agent_name.ts
@@ -30,10 +30,12 @@ export function isAgentName(agentName: string): agentName is AgentName {
return AGENT_NAMES.includes(agentName as AgentName);
}
+export const RUM_AGENTS = ['js-base', 'rum-js'];
+
export function isRumAgentName(
- agentName: string | undefined
+ agentName?: string
): agentName is 'js-base' | 'rum-js' {
- return agentName === 'js-base' || agentName === 'rum-js';
+ return RUM_AGENTS.includes(agentName!);
}
export function isJavaAgentName(
diff --git a/x-pack/plugins/apm/public/components/app/Home/index.tsx b/x-pack/plugins/apm/public/components/app/Home/index.tsx
index 69699b72a96df..f612ac0d383ef 100644
--- a/x-pack/plugins/apm/public/components/app/Home/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/Home/index.tsx
@@ -27,7 +27,6 @@ import { ServiceOverview } from '../ServiceOverview';
import { TraceOverview } from '../TraceOverview';
import { RumOverview } from '../RumDashboard';
import { RumOverviewLink } from '../../shared/Links/apm/RumOverviewLink';
-import { I18LABELS } from '../RumDashboard/translations';
function getHomeTabs({
serviceMapEnabled = true,
@@ -109,11 +108,7 @@ export function Home({ tab }: Props) {
-