diff --git a/docs/apm/api.asciidoc b/docs/apm/api.asciidoc index b26c7446b91d..3e4b515e009d 100644 --- a/docs/apm/api.asciidoc +++ b/docs/apm/api.asciidoc @@ -9,14 +9,45 @@ Some APM app features are provided via a REST API: * <> - -TIP: Kibana provides additional <>, -and general information on <>. +* <> + +Here's an example CURL request that adds an annotation to the APM app: + +[source,curl] +---- +curl -X POST \ + http://localhost:5601/api/apm/services/opbeans-java/annotation \ +-H 'Content-Type: application/json' \ +-H 'kbn-xsrf: true' \ +-H 'Authorization: Basic YhUlubWZhM0FDbnlQeE6WRtaW49FQmSGZ4RUWXdX' \ +-d '{ + "@timestamp": "2020-05-11T10:31:30.452Z", + "service": { + "version": "1.2" + }, + "message": "Revert upgrade", + "tags": [ + "elastic.co", "customer" + ] + }' +---- + +For more information, the Kibana <> provides information on how to use Kibana APIs, +like required request headers and authentication options. + +// AGENT CONFIG API +// GET --> Feature (APM) Read +// CREATE/EDIT/DELETE --> Feature (APM) All + +// ANNOTATION API +// Feature (APM) All +// Index: `observability-annotations`. Privileges: `create_index`, `create_doc`, `manage`, and `read`. //// ******************************************************* //// +[role="xpack"] [[agent-config-api]] === Agent Configuration API @@ -274,6 +305,118 @@ POST /api/apm/settings/agent-configuration/search } -------------------------------------------------- +//// +******************************************************* +******************************************************* +//// + +[role="xpack"] +[[apm-annotation-api]] +=== Annotation API + +The Annotation API allows you to annotate visualizations in the APM app with significant events, like deployments, +allowing you to easily see how these events are impacting the performance of your existing applications. + +The following APIs are available: + +* <> to create an annotation for APM. +// * <> POST /api/observability/annotation +// * <> GET /api/observability/annotation/:id +// * <> DELETE /api/observability/annotation/:id + +By default, annotations are stored in a newly created `observability-annotations` index. +The name of this index can be changed in your `config.yml` by editing `xpack.observability.annotations.index`. + //// ******************************************************* //// + +[[apm-annotation-create]] +==== Create or update annotation + +[[apm-annotation-config-req]] +===== Request + +`POST /api/apm/services/:serviceName/annotation` + +[role="child_attributes"] +[[apm-annotation-config-req-body]] +===== Request body + +`service`:: +(required, object) Service identifying the configuration to create or update. ++ +.Properties of `service` +[%collapsible%open] +====== +`version` ::: + (required, string) Name of service. + +`environment` ::: + (optional, string) Environment of service. +====== + +`@timestamp`:: +(required, string) The date and time of the annotation. Must be in https://www.w3.org/TR/NOTE-datetime[ISO 8601] format. + +`message`:: +(optional, string) The message displayed in the annotation. Defaults to `service.version`. + +`tags`:: +(optional, array) Tags are used by the APM app to distinguish APM annotations from other annotations. +Tags may have additional functionality in future releases. Defaults to `[apm]`. +While you can add additional tags, you cannot remove the `apm` tag. + +[[apm-annotation-config-example]] +===== Example + +The following example creates an annotation for a service named `opbeans-java`. + +[source,console] +-------------------------------------------------- +POST /api/apm/services/opbeans-java/annotation +{ + "@timestamp": "2020-05-08T10:31:30.452Z", + "service": { + "version": "1.2" + }, + "message": "Deployment 1.2", + "tags": [ + "elastic.co", "customer" + ] +} +-------------------------------------------------- + +[[apm-annotation-config-body]] +===== Response body + +[source,js] +-------------------------------------------------- +{ + "_index": "observability-annotations", + "_id": "Lc9I93EBh6DbmkeV7nFX", + "_version": 1, + "_seq_no": 12, + "_primary_term": 1, + "found": true, + "_source": { + "message": "Deployment 1.2", + "@timestamp": "2020-05-08T10:31:30.452Z", + "service": { + "version": "1.2", + "name": "opbeans-java" + }, + "tags": [ + "apm", + "elastic.co", + "customer" + ], + "annotation": { + "type": "deployment" + }, + "event": { + "created": "2020-05-09T02:34:43.937Z" + } + } +} +-------------------------------------------------- diff --git a/docs/apm/deployment-annotations.asciidoc b/docs/apm/deployment-annotations.asciidoc index 6feadf846322..9abcd9f6efc7 100644 --- a/docs/apm/deployment-annotations.asciidoc +++ b/docs/apm/deployment-annotations.asciidoc @@ -3,7 +3,7 @@ === Track deployments with annotations ++++ -Track deployments +Track deployments with annotations ++++ For enhanced visibility into your deployments, we offer deployment annotations on all transaction charts. @@ -11,7 +11,11 @@ This feature automatically tags new deployments, so you can easily see if your d for an end-user, or if the memory/CPU footprint of your application has changed. Being able to identify bad deployments quickly enables you to rollback and fix issues without causing costly outages. -Deployment annotations are automatically enabled, and appear when the `service.version` of your app changes. +Deployment annotations are enabled by default, and can be created with the <>. +If there are no created annotations for the selected time period, +the APM app will automatically annotate your data if the `service.version` of your application changes. + +NOTE: If custom annotations have been created for the selected time period, any derived annotations, i.e., those created automatically when `service.version` changes, will not be shown. [role="screenshot"] image::apm/images/apm-transaction-annotation.png[Example view of transactions annotation in the APM app in Kibana] diff --git a/docs/apm/images/apm-transaction-annotation.png b/docs/apm/images/apm-transaction-annotation.png index bc71b1d2169c..8913770517ff 100644 Binary files a/docs/apm/images/apm-transaction-annotation.png and b/docs/apm/images/apm-transaction-annotation.png differ diff --git a/docs/settings/apm-settings.asciidoc b/docs/settings/apm-settings.asciidoc index efc43e5d2f52..b78193048524 100644 --- a/docs/settings/apm-settings.asciidoc +++ b/docs/settings/apm-settings.asciidoc @@ -52,6 +52,9 @@ Changing these settings may disable features of the APM App. | `xpack.apm.ui.maxTraceItems` {ess-icon} | Maximum number of child items displayed when viewing trace details. Defaults to `1000`. +| `xpack.observability.annotations.index` + | Index name where Observability annotations are stored. Defaults to `observability-annotations`. + | `apm_oss.indexPattern` {ess-icon} | The index pattern used for integrations with Machine Learning and Query Bar. It must match all apm indices. Defaults to `apm-*`. diff --git a/package.json b/package.json index 7ff3ea6dc355..634720d6d3a2 100644 --- a/package.json +++ b/package.json @@ -298,7 +298,6 @@ "@elastic/eslint-plugin-eui": "0.0.2", "@elastic/github-checks-reporter": "0.0.20b3", "@elastic/makelogs": "^5.0.1", - "@elastic/static-fs": "1.0.2", "@kbn/dev-utils": "1.0.0", "@kbn/es": "1.0.0", "@kbn/eslint-import-resolver-kibana": "2.0.0", diff --git a/packages/kbn-optimizer/src/run_optimizer.ts b/packages/kbn-optimizer/src/run_optimizer.ts index ab12cc679bc6..37260e942717 100644 --- a/packages/kbn-optimizer/src/run_optimizer.ts +++ b/packages/kbn-optimizer/src/run_optimizer.ts @@ -39,10 +39,26 @@ export type OptimizerUpdate = Update; export type OptimizerUpdate$ = Rx.Observable; export function runOptimizer(config: OptimizerConfig) { - return Rx.defer(async () => ({ - startTime: Date.now(), - cacheKey: await getOptimizerCacheKey(config), - })).pipe( + return Rx.defer(async () => { + if (process.platform === 'darwin') { + try { + require.resolve('fsevents'); + } catch (error) { + if (error.code === 'MODULE_NOT_FOUND') { + throw new Error( + '`fsevents` module is not installed, most likely because you need to follow the instructions at https://github.com/nodejs/node-gyp/blob/master/macOS_Catalina.md and re-bootstrap Kibana' + ); + } + + throw error; + } + } + + return { + startTime: Date.now(), + cacheKey: await getOptimizerCacheKey(config), + }; + }).pipe( mergeMap(({ startTime, cacheKey }) => { const bundleCacheEvent$ = getBundleCacheEvent$(config, cacheKey).pipe( observeOn(Rx.asyncScheduler), diff --git a/scripts/kibana.js b/scripts/kibana.js index 4da739469ffb..f5a63e6c07dd 100644 --- a/scripts/kibana.js +++ b/scripts/kibana.js @@ -17,6 +17,6 @@ * under the License. */ -require('../src/setup_node_env'); require('../src/apm')(process.env.ELASTIC_APM_PROXY_SERVICE_NAME || 'kibana-proxy'); +require('../src/setup_node_env'); require('../src/cli/cli'); diff --git a/src/cli/index.js b/src/cli/index.js index 6dbdd800268a..45f88eaf82a5 100644 --- a/src/cli/index.js +++ b/src/cli/index.js @@ -17,6 +17,6 @@ * under the License. */ -require('../setup_node_env'); require('../apm')(); +require('../setup_node_env'); require('./cli'); diff --git a/src/dev/build/build_distributables.js b/src/dev/build/build_distributables.js index 910313ac8705..6c2efeebc60c 100644 --- a/src/dev/build/build_distributables.js +++ b/src/dev/build/build_distributables.js @@ -40,7 +40,6 @@ import { CreatePackageJsonTask, CreateReadmeTask, CreateRpmPackageTask, - CreateStaticFsWithNodeModulesTask, DownloadNodeBuildsTask, ExtractNodeBuildsTask, InstallDependenciesTask, @@ -127,7 +126,6 @@ export async function buildDistributables(options) { await run(CleanTypescriptTask); await run(CleanExtraFilesFromModulesTask); await run(CleanEmptyFoldersTask); - await run(CreateStaticFsWithNodeModulesTask); /** * copy generic build outputs into platform-specific build diff --git a/src/dev/build/tasks/create_static_fs_with_node_modules_task.js b/src/dev/build/tasks/create_static_fs_with_node_modules_task.js deleted file mode 100644 index 0ab296fc5c16..000000000000 --- a/src/dev/build/tasks/create_static_fs_with_node_modules_task.js +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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 del from 'del'; -import globby from 'globby'; -import { resolve } from 'path'; -import { generateStaticFsVolume } from '@elastic/static-fs'; - -async function deletePathsList(list) { - for (const path of list) { - await del(path); - } -} - -async function getTopLevelNodeModulesFolders(rootDir) { - const nodeModulesFoldersForCwd = await globby(['**/node_modules', '!**/node_modules/**/*'], { - cwd: rootDir, - onlyDirectories: true, - }); - - return nodeModulesFoldersForCwd.map(folder => resolve(rootDir, folder)); -} - -export const CreateStaticFsWithNodeModulesTask = { - description: - 'Creating static filesystem with node_modules, patching entryPoints and deleting node_modules folder', - - async run(config, log, build) { - const rootDir = build.resolvePath('.'); - - // Get all the top node_modules folders - const nodeModulesFolders = await getTopLevelNodeModulesFolders(rootDir); - - // Define root entry points - const rootEntryPoints = [build.resolvePath('src/setup_node_env/index.js')]; - - // Creates the static filesystem with - // every node_module we have - const staticFsAddedPaths = await generateStaticFsVolume( - rootDir, - nodeModulesFolders, - rootEntryPoints - ); - - // Delete node_modules folder - await deletePathsList(staticFsAddedPaths); - }, -}; diff --git a/src/dev/build/tasks/index.js b/src/dev/build/tasks/index.js index 0232ac4b1b5f..8105fa8a7d5d 100644 --- a/src/dev/build/tasks/index.js +++ b/src/dev/build/tasks/index.js @@ -25,7 +25,6 @@ export * from './create_archives_task'; export * from './create_empty_dirs_and_files_task'; export * from './create_package_json_task'; export * from './create_readme_task'; -export * from './create_static_fs_with_node_modules_task'; export * from './install_dependencies_task'; export * from './license_file_task'; export * from './nodejs'; diff --git a/src/plugins/vis_type_timeseries/public/application/lib/validate_interval.js b/src/plugins/vis_type_timeseries/public/application/lib/validate_interval.js index 40fd4d871a96..e8ddb4ceb5cb 100644 --- a/src/plugins/vis_type_timeseries/public/application/lib/validate_interval.js +++ b/src/plugins/vis_type_timeseries/public/application/lib/validate_interval.js @@ -40,8 +40,7 @@ export function validateInterval(bounds, panel, maxBuckets) { 'visTypeTimeseries.validateInterval.notifier.maxBucketsExceededErrorMessage', { defaultMessage: - 'Max buckets exceeded: {buckets} is greater than {maxBuckets}, try a larger time interval in the panel options.', - values: { buckets, maxBuckets }, + 'Your query attempted to fetch too much data. Reducing the time range or changing the interval used usually fixes the issue.', } ) ); diff --git a/test/visual_regression/tests/discover/chart_visualization.ts b/test/visual_regression/tests/discover/chart_visualization.ts index 49c3057a27cb..e9ef1dffd897 100644 --- a/test/visual_regression/tests/discover/chart_visualization.ts +++ b/test/visual_regression/tests/discover/chart_visualization.ts @@ -72,18 +72,18 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { await takeSnapshot(); }); - it('should show correct data for chart interval Hourly', async function() { - await PageObjects.discover.setChartInterval('Hourly'); + it('should show correct data for chart interval Hour', async function() { + await PageObjects.discover.setChartInterval('Hour'); await takeSnapshot(); }); - it('should show correct data for chart interval Daily', async function() { - await PageObjects.discover.setChartInterval('Daily'); + it('should show correct data for chart interval Day', async function() { + await PageObjects.discover.setChartInterval('Day'); await takeSnapshot(); }); - it('should show correct data for chart interval Weekly', async function() { - await PageObjects.discover.setChartInterval('Weekly'); + it('should show correct data for chart interval Week', async function() { + await PageObjects.discover.setChartInterval('Week'); await takeSnapshot(); }); diff --git a/x-pack/dev-tools/jest/create_jest_config.js b/x-pack/dev-tools/jest/create_jest_config.js index 75f8a3c156e0..3d8b45e7d1b8 100644 --- a/x-pack/dev-tools/jest/create_jest_config.js +++ b/x-pack/dev-tools/jest/create_jest_config.js @@ -18,6 +18,7 @@ export function createJestConfig({ kibanaDirectory, xPackKibanaDirectory }) { 'uiExports/(.*)': fileMockPath, '^src/core/(.*)': `${kibanaDirectory}/src/core/$1`, '^src/legacy/(.*)': `${kibanaDirectory}/src/legacy/$1`, + '^src/plugins/(.*)': `${kibanaDirectory}/src/plugins/$1`, '^plugins/([^/.]*)(.*)': `${kibanaDirectory}/src/legacy/core_plugins/$1/public$2`, '^legacy/plugins/xpack_main/(.*);': `${xPackKibanaDirectory}/legacy/plugins/xpack_main/public/$1`, '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': fileMockPath, diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index 083536563599..4d64a1cd9fcf 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -88,8 +88,8 @@ export interface AlertingPluginsSetup { } export interface AlertingPluginsStart { actions: ActionsPluginStartContract; - encryptedSavedObjects: EncryptedSavedObjectsPluginStart; taskManager: TaskManagerStartContract; + encryptedSavedObjects: EncryptedSavedObjectsPluginStart; } export class AlertingPlugin { @@ -126,6 +126,7 @@ export class AlertingPlugin { this.licenseState = new LicenseState(plugins.licensing.license$); this.spaces = plugins.spaces?.spacesService; this.security = plugins.security; + this.isESOUsingEphemeralEncryptionKey = plugins.encryptedSavedObjects.usingEphemeralEncryptionKey; @@ -164,7 +165,7 @@ export class AlertingPlugin { }); } - core.http.registerRouteHandlerContext('alerting', this.createRouteHandlerContext()); + core.http.registerRouteHandlerContext('alerting', this.createRouteHandlerContext(core)); // Routes const router = core.http.createRouter(); @@ -201,7 +202,9 @@ export class AlertingPlugin { security, } = this; - const encryptedSavedObjectsClient = plugins.encryptedSavedObjects.getClient(); + const encryptedSavedObjectsClient = plugins.encryptedSavedObjects.getClient({ + includedHiddenTypes: ['alert'], + }); alertsClientFactory.initialize({ alertTypeRegistry: alertTypeRegistry!, @@ -231,26 +234,32 @@ export class AlertingPlugin { return { listTypes: alertTypeRegistry!.list.bind(this.alertTypeRegistry!), // Ability to get an alerts client from legacy code - getAlertsClientWithRequest(request: KibanaRequest) { + getAlertsClientWithRequest: (request: KibanaRequest) => { if (isESOUsingEphemeralEncryptionKey === true) { throw new Error( `Unable to create alerts client due to the Encrypted Saved Objects plugin using an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in kibana.yml` ); } - return alertsClientFactory!.create(request, core.savedObjects.getScopedClient(request)); + return alertsClientFactory!.create( + request, + this.getScopedClientWithAlertSavedObjectType(core.savedObjects, request) + ); }, }; } - private createRouteHandlerContext = (): IContextProvider< - RequestHandler, - 'alerting' - > => { + private createRouteHandlerContext = ( + core: CoreSetup + ): IContextProvider, 'alerting'> => { const { alertTypeRegistry, alertsClientFactory } = this; - return async function alertsRouteHandlerContext(context, request) { + return async (context, request) => { + const [{ savedObjects }] = await core.getStartServices(); return { getAlertsClient: () => { - return alertsClientFactory!.create(request, context.core.savedObjects.client); + return alertsClientFactory!.create( + request, + this.getScopedClientWithAlertSavedObjectType(savedObjects, request) + ); }, listTypes: alertTypeRegistry!.list.bind(alertTypeRegistry!), }; @@ -263,7 +272,7 @@ export class AlertingPlugin { ): (request: KibanaRequest) => Services { return request => ({ callCluster: elasticsearch.legacy.client.asScoped(request).callAsCurrentUser, - savedObjectsClient: savedObjects.getScopedClient(request), + savedObjectsClient: this.getScopedClientWithAlertSavedObjectType(savedObjects, request), getScopedCallCluster(clusterClient: IClusterClient) { return clusterClient.asScoped(request).callAsCurrentUser; }, @@ -278,6 +287,13 @@ export class AlertingPlugin { return this.spaces && spaceId ? this.spaces.getBasePath(spaceId) : this.serverBasePath!; }; + private getScopedClientWithAlertSavedObjectType( + savedObjects: SavedObjectsServiceStart, + request: KibanaRequest + ) { + return savedObjects.getScopedClient(request, { includedHiddenTypes: ['alert'] }); + } + public stop() { if (this.licenseState) { this.licenseState.clean(); diff --git a/x-pack/plugins/alerting/server/saved_objects/index.ts b/x-pack/plugins/alerting/server/saved_objects/index.ts index 4efec2fe55ef..c98d9bcbd9ae 100644 --- a/x-pack/plugins/alerting/server/saved_objects/index.ts +++ b/x-pack/plugins/alerting/server/saved_objects/index.ts @@ -14,7 +14,7 @@ export function setupSavedObjects( ) { savedObjects.registerType({ name: 'alert', - hidden: false, + hidden: true, namespaceType: 'single', mappings: mappings.alert, }); diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx index 67c6cd9978bf..441f4f6bf46b 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx @@ -80,7 +80,7 @@ function getLayoutOptions( roots: selectedRoots.length ? selectedRoots : undefined, fit: true, padding: nodeHeight, - spacingFactor: 0.85, + spacingFactor: 1.2, // @ts-ignore // Rotate nodes counter-clockwise to transform layout from top→bottom to left→right. // The extra 5° achieves the effect of separating overlapping taxi-styled edges. diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts b/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts index 3ec13a4cde20..67cd688b6d18 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts @@ -117,7 +117,7 @@ const style: cytoscape.Stylesheet[] = [ // theme.euiFontFamily doesn't work here for some reason, so we're just // specifying a subset of the fonts for the label text. 'font-family': 'Inter UI, Segoe UI, Helvetica, Arial, sans-serif', - 'font-size': theme.euiFontSizeXS, + 'font-size': theme.euiFontSizeS, ghost: 'yes', 'ghost-offset-x': 0, 'ghost-offset-y': 2, @@ -127,7 +127,7 @@ const style: cytoscape.Stylesheet[] = [ isService(el) ? el.data(SERVICE_NAME) : el.data(SPAN_DESTINATION_SERVICE_RESOURCE), - 'min-zoomed-font-size': parseInt(theme.euiSizeL, 10), + 'min-zoomed-font-size': parseInt(theme.euiSizeS, 10), 'overlay-opacity': 0, shape: (el: cytoscape.NodeSingular) => isService(el) ? (isIE11 ? 'rectangle' : 'ellipse') : 'diamond', diff --git a/x-pack/plugins/canvas/public/components/fullscreen/fullscreen.scss b/x-pack/plugins/canvas/public/components/fullscreen/fullscreen.scss index 2c26c7314f37..c198884ee713 100644 --- a/x-pack/plugins/canvas/public/components/fullscreen/fullscreen.scss +++ b/x-pack/plugins/canvas/public/components/fullscreen/fullscreen.scss @@ -1,4 +1,13 @@ body.canvas-isFullscreen { // sass-lint:disable-line no-qualifying-elements + // following two rules are for overriding the header bar padding + &.euiBody--headerIsFixed { + padding-top: 0; + } + + .headerWrapper ~ .app-wrapper { + min-height: 100vh; + } + // hide global loading indicator .kbnLoadingIndicator { display: none; diff --git a/x-pack/plugins/canvas/public/plugin.tsx b/x-pack/plugins/canvas/public/plugin.tsx index c2192818e528..1265bfbb69b7 100644 --- a/x-pack/plugins/canvas/public/plugin.tsx +++ b/x-pack/plugins/canvas/public/plugin.tsx @@ -74,10 +74,12 @@ export class CanvasPlugin this.srcPlugin.setup(core, { canvas: canvasApi }); // Set the nav link to the last saved url if we have one in storage - const lastUrl = getSessionStorage().get(SESSIONSTORAGE_LASTPATH); - if (lastUrl) { + const lastPath = getSessionStorage().get( + `${SESSIONSTORAGE_LASTPATH}:${core.http.basePath.get()}` + ); + if (lastPath) { this.appUpdater.next(() => ({ - defaultPath: `#${lastUrl}`, + defaultPath: `#${lastPath}`, })); } diff --git a/x-pack/plugins/canvas/public/services/nav_link.ts b/x-pack/plugins/canvas/public/services/nav_link.ts index 506149845800..68d685242351 100644 --- a/x-pack/plugins/canvas/public/services/nav_link.ts +++ b/x-pack/plugins/canvas/public/services/nav_link.ts @@ -25,7 +25,7 @@ export const navLinkServiceFactory: CanvasServiceFactory = ( defaultPath: `#${path}`, })); - getSessionStorage().set(SESSIONSTORAGE_LASTPATH, path); + getSessionStorage().set(`${SESSIONSTORAGE_LASTPATH}:${coreSetup.http.basePath.get()}`, path); }, }; }; diff --git a/x-pack/plugins/encrypted_saved_objects/README.md b/x-pack/plugins/encrypted_saved_objects/README.md index 6085b52d392a..2f0af9e86679 100644 --- a/x-pack/plugins/encrypted_saved_objects/README.md +++ b/x-pack/plugins/encrypted_saved_objects/README.md @@ -68,7 +68,19 @@ router.get( ... ``` -5. To retrieve Saved Object with decrypted content use the dedicated `getDecryptedAsInternalUser` API method. +5. Instantiate an EncryptedSavedObjects client so that you can interact with Saved Objects whose content has been encrypted. + +```typescript +const esoClient = encryptedSavedObjects.getClient(); +``` + +If your SavedObject type is a _hidden_ type, then you will have to specify it as an included type: + +```typescript +const esoClient = encryptedSavedObjects.getClient({ includedHiddenTypes: ['myHiddenType'] }); +``` + +6. To retrieve Saved Object with decrypted content use the dedicated `getDecryptedAsInternalUser` API method. **Note:** As name suggests the method will retrieve the encrypted values and decrypt them on behalf of the internal Kibana user to make it possible to use this method even when user request context is not available (e.g. in background tasks). @@ -77,7 +89,7 @@ and preferably only as a part of the Kibana server routines that are outside of user has control over. ```typescript -const savedObjectWithDecryptedContent = await encryptedSavedObjects.getDecryptedAsInternalUser( +const savedObjectWithDecryptedContent = await esoClient.getDecryptedAsInternalUser( 'my-saved-object-type', 'saved-object-id' ); diff --git a/x-pack/plugins/encrypted_saved_objects/server/mocks.ts b/x-pack/plugins/encrypted_saved_objects/server/mocks.ts index bbc3eb154056..d5550703cf76 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/mocks.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/mocks.ts @@ -5,7 +5,7 @@ */ import { EncryptedSavedObjectsPluginSetup, EncryptedSavedObjectsPluginStart } from './plugin'; -import { EncryptedSavedObjectsClient } from './saved_objects'; +import { EncryptedSavedObjectsClient, EncryptedSavedObjectsClientOptions } from './saved_objects'; function createEncryptedSavedObjectsSetupMock() { return { @@ -18,11 +18,11 @@ function createEncryptedSavedObjectsSetupMock() { function createEncryptedSavedObjectsStartMock() { return { isEncryptionError: jest.fn(), - getClient: jest.fn(() => createEncryptedSavedObjectsClienttMock()), + getClient: jest.fn(opts => createEncryptedSavedObjectsClienttMock(opts)), } as jest.Mocked; } -function createEncryptedSavedObjectsClienttMock() { +function createEncryptedSavedObjectsClienttMock(opts?: EncryptedSavedObjectsClientOptions) { return { getDecryptedAsInternalUser: jest.fn(), } as jest.Mocked; diff --git a/x-pack/plugins/encrypted_saved_objects/server/plugin.ts b/x-pack/plugins/encrypted_saved_objects/server/plugin.ts index 948cb94512f2..83b412de5db7 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/plugin.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/plugin.ts @@ -14,7 +14,7 @@ import { EncryptionError, } from './crypto'; import { EncryptedSavedObjectsAuditLogger } from './audit'; -import { SavedObjectsSetup, setupSavedObjects } from './saved_objects'; +import { setupSavedObjects, ClientInstanciator } from './saved_objects'; export interface PluginsSetup { security?: SecurityPluginSetup; @@ -28,7 +28,7 @@ export interface EncryptedSavedObjectsPluginSetup { export interface EncryptedSavedObjectsPluginStart { isEncryptionError: (error: Error) => boolean; - getClient: SavedObjectsSetup; + getClient: ClientInstanciator; } /** @@ -46,7 +46,7 @@ export interface LegacyAPI { */ export class Plugin { private readonly logger: Logger; - private savedObjectsSetup!: SavedObjectsSetup; + private savedObjectsSetup!: ClientInstanciator; private legacyAPI?: LegacyAPI; private readonly getLegacyAPI = () => { @@ -95,7 +95,7 @@ export class Plugin { this.logger.debug('Starting plugin'); return { isEncryptionError: (error: Error) => error instanceof EncryptionError, - getClient: (includedHiddenTypes?: string[]) => this.savedObjectsSetup(includedHiddenTypes), + getClient: (options = {}) => this.savedObjectsSetup(options), }; } diff --git a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.test.ts b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.test.ts index 8f0eb855676a..8e9f12268cd7 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.test.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObjectsSetup, setupSavedObjects } from '.'; +import { ClientInstanciator, setupSavedObjects } from '.'; import { coreMock, @@ -24,7 +24,7 @@ import { import { EncryptedSavedObjectsService } from '../crypto'; describe('#setupSavedObjects', () => { - let setupContract: SavedObjectsSetup; + let setupContract: ClientInstanciator; let coreStartMock: ReturnType; let coreSetupMock: ReturnType; let mockSavedObjectsRepository: jest.Mocked; @@ -91,7 +91,7 @@ describe('#setupSavedObjects', () => { describe('#setupContract', () => { it('includes hiddenTypes when specified', async () => { - await setupContract(['hiddenType']); + await setupContract({ includedHiddenTypes: ['hiddenType'] }); expect(coreStartMock.savedObjects.createInternalRepository).toHaveBeenCalledWith([ 'hiddenType', ]); diff --git a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.ts b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.ts index 67bbaab75425..9ab3e85cc862 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.ts @@ -23,7 +23,13 @@ interface SetupSavedObjectsParams { getStartServices: StartServicesAccessor; } -export type SavedObjectsSetup = (includedHiddenTypes?: string[]) => EncryptedSavedObjectsClient; +export type ClientInstanciator = ( + options?: EncryptedSavedObjectsClientOptions +) => EncryptedSavedObjectsClient; + +export interface EncryptedSavedObjectsClientOptions { + includedHiddenTypes?: string[]; +} export interface EncryptedSavedObjectsClient { getDecryptedAsInternalUser: ( @@ -38,7 +44,7 @@ export function setupSavedObjects({ savedObjects, security, getStartServices, -}: SetupSavedObjectsParams): SavedObjectsSetup { +}: SetupSavedObjectsParams): ClientInstanciator { // Register custom saved object client that will encrypt, decrypt and strip saved object // attributes where appropriate for any saved object repository request. We choose max possible // priority for this wrapper to allow all other wrappers to set proper `namespace` for the Saved @@ -56,11 +62,11 @@ export function setupSavedObjects({ }) ); - return (includedHiddenTypes?: string[]) => { + return clientOpts => { const internalRepositoryAndTypeRegistryPromise = getStartServices().then( ([core]) => [ - core.savedObjects.createInternalRepository(includedHiddenTypes), + core.savedObjects.createInternalRepository(clientOpts?.includedHiddenTypes), core.savedObjects.getTypeRegistry(), ] as [ISavedObjectsRepository, ISavedObjectTypeRegistry] ); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/home.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/home.helpers.ts index eb4a604779a2..a323fdf714d8 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/home.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/home.helpers.ts @@ -22,7 +22,7 @@ import { WithAppDependencies, services } from './setup_environment'; const testBedConfig: TestBedConfig = { store: () => indexManagementStore(services as any), memoryRouter: { - initialEntries: [`${BASE_PATH}indices`], + initialEntries: [`${BASE_PATH}indices?includeHidden=true`], componentRoutePath: `${BASE_PATH}:section(indices|templates)`, }, doMountAsync: true, @@ -44,6 +44,8 @@ export interface IdxMgmtHomeTestBed extends TestBed { clickTemplateAt: (index: number) => void; clickCloseDetailsButton: () => void; clickActionMenu: (name: TemplateDeserialized['name']) => void; + getIncludeHiddenIndicesToggleStatus: () => boolean; + clickIncludeHiddenIndicesToggle: () => void; }; } @@ -124,6 +126,17 @@ export const setup = async (): Promise => { find('closeDetailsButton').simulate('click'); }; + const clickIncludeHiddenIndicesToggle = () => { + const { find } = testBed; + find('indexTableIncludeHiddenIndicesToggle').simulate('click'); + }; + + const getIncludeHiddenIndicesToggleStatus = () => { + const { find } = testBed; + const props = find('indexTableIncludeHiddenIndicesToggle').props(); + return Boolean(props['aria-checked']); + }; + const selectIndexDetailsTab = async ( tab: 'settings' | 'mappings' | 'stats' | 'edit_settings' ) => { @@ -149,6 +162,8 @@ export const setup = async (): Promise => { clickTemplateAt, clickCloseDetailsButton, clickActionMenu, + getIncludeHiddenIndicesToggleStatus, + clickIncludeHiddenIndicesToggle, }, }; }; @@ -173,6 +188,7 @@ export type TestSubjects = | 'noSettingsCallout' | 'indicesList' | 'indicesTab' + | 'indexTableIncludeHiddenIndicesToggle' | 'indexTableIndexNameLink' | 'reloadButton' | 'reloadIndicesButton' diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home.test.ts index 54345077949b..4c5cfcd82684 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home.test.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home.test.ts @@ -44,6 +44,18 @@ describe('', () => { }); }); + test('sets the hash query param base on include hidden indices toggle', () => { + const { actions } = testBed; + expect(actions.getIncludeHiddenIndicesToggleStatus()).toBe(true); + expect(window.location.hash.includes('includeHidden=true')).toBe(true); + actions.clickIncludeHiddenIndicesToggle(); + expect(window.location.hash.includes('includeHidden=true')).toBe(false); + // Note: this test modifies the shared location.hash state, we put it back the way it was + actions.clickIncludeHiddenIndicesToggle(); + expect(actions.getIncludeHiddenIndicesToggleStatus()).toBe(true); + expect(window.location.hash.includes('includeHidden=true')).toBe(true); + }); + test('should set the correct app title', () => { const { exists, find } = testBed; expect(exists('appTitle')).toBe(true); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx index 46735f0e741e..e708bf1b4de6 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx @@ -24,7 +24,8 @@ export const defaultTextParameters = { store: false, }; -describe('Mappings editor: text datatype', () => { +// FLAKY: https://github.com/elastic/kibana/issues/66669 +describe.skip('Mappings editor: text datatype', () => { let testBed: MappingsEditorTestBed; /** diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_list.js b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_list.js index 36999bc40e2a..4b58ff0de17e 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_list.js +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_list.js @@ -12,10 +12,11 @@ export function IndexList({ match: { params: { filter }, }, + location, }) { return (
- +
); diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js index 7a0a6269a6ab..799f3a6bcb53 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js @@ -8,6 +8,7 @@ import React, { Component, Fragment } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { Route } from 'react-router-dom'; +import { parse } from 'query-string'; import { EuiButton, @@ -93,10 +94,18 @@ export class IndexTable extends Component { selectedIndicesMap: {}, }; } + componentDidMount() { this.props.loadIndices(); this.interval = setInterval(this.props.reloadIndices, REFRESH_RATE_INDEX_LIST); - const { filterChanged, filterFromURI } = this.props; + const { + filterChanged, + filterFromURI, + showHiddenIndicesChanged, + showHiddenIndices, + location, + } = this.props; + if (filterFromURI) { const decodedFilter = decodeURIComponent(filterFromURI); @@ -107,6 +116,13 @@ export class IndexTable extends Component { this.setState({ filterError: e }); } } + + // Check if the we have the includeHidden query param + const { includeHidden } = parse((location && location.search) || ''); + const nextValue = includeHidden === 'true'; + if (nextValue !== showHiddenIndices) { + showHiddenIndicesChanged(nextValue); + } } componentWillUnmount() { clearInterval(this.interval); @@ -460,6 +476,7 @@ export class IndexTable extends Component { showHiddenIndicesChanged(event.target.checked)} label={ diff --git a/x-pack/plugins/index_management/public/application/store/middlewares/index.ts b/x-pack/plugins/index_management/public/application/store/middlewares/index.ts new file mode 100644 index 000000000000..06d77a9c3d34 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/store/middlewares/index.ts @@ -0,0 +1,7 @@ +/* + * 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. + */ + +export { syncUrlHashQueryParam } from './sync_url_hash_query_param.js'; diff --git a/x-pack/plugins/index_management/public/application/store/middlewares/sync_url_hash_query_param.js.ts b/x-pack/plugins/index_management/public/application/store/middlewares/sync_url_hash_query_param.js.ts new file mode 100644 index 000000000000..6dd2efe93514 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/store/middlewares/sync_url_hash_query_param.js.ts @@ -0,0 +1,22 @@ +/* + * 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 * as q from 'query-string'; +import { Middleware } from 'redux'; +// @ts-ignore +import { showHiddenIndicesChanged } from '../actions'; + +export const syncUrlHashQueryParam: Middleware = () => next => action => { + if (action.type === String(showHiddenIndicesChanged)) { + const { url, query } = q.parseUrl(window.location.hash); + if (action.payload.showHiddenIndices) { + query.includeHidden = 'true'; + } else { + delete query.includeHidden; + } + window.location.hash = url + '?' + q.stringify(query); + } + next(action); +}; diff --git a/x-pack/plugins/index_management/public/application/store/store.js b/x-pack/plugins/index_management/public/application/store/store.js index e103ff0778be..26a9ff8f997f 100644 --- a/x-pack/plugins/index_management/public/application/store/store.js +++ b/x-pack/plugins/index_management/public/application/store/store.js @@ -9,6 +9,7 @@ import thunk from 'redux-thunk'; import { defaultTableState } from './reducers/table_state'; import { getReducer } from './reducers/'; +import { syncUrlHashQueryParam } from './middlewares'; export function indexManagementStore(services) { const toggleNameToVisibleMap = {}; @@ -16,7 +17,7 @@ export function indexManagementStore(services) { toggleNameToVisibleMap[toggleExtension.name] = false; }); const initialState = { tableState: { ...defaultTableState, toggleNameToVisibleMap } }; - const enhancers = [applyMiddleware(thunk)]; + const enhancers = [applyMiddleware(thunk, syncUrlHashQueryParam)]; window.__REDUX_DEVTOOLS_EXTENSION__ && enhancers.push(window.__REDUX_DEVTOOLS_EXTENSION__()); return createStore(getReducer(services), initialState, compose(...enhancers)); diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 73b79b2f4cce..a3a134978582 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -3744,7 +3744,6 @@ "visTypeTimeseries.unsupportedAgg.aggIsNotSupportedDescription": "{modelType} 集約はサポートされなくなりました。", "visTypeTimeseries.unsupportedAgg.aggIsTemporaryUnsupportedDescription": "{modelType} 集約は現在サポートされていません。", "visTypeTimeseries.unsupportedSplit.splitIsUnsupportedDescription": "{modelType} による分割はサポートされていません。", - "visTypeTimeseries.validateInterval.notifier.maxBucketsExceededErrorMessage": "バケットの最高数を超えました。{buckets} が {maxBuckets} を超えています。パネルオプションでより広い間隔を試してみてください。", "visTypeTimeseries.vars.variableNameAriaLabel": "変数名", "visTypeTimeseries.vars.variableNamePlaceholder": "変数名", "visTypeTimeseries.visEditorVisualization.applyChangesLabel": "変更を適用", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index d0a18d2f40cf..57e76768a2cf 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -3745,7 +3745,6 @@ "visTypeTimeseries.unsupportedAgg.aggIsNotSupportedDescription": "不再支持 {modelType} 聚合。", "visTypeTimeseries.unsupportedAgg.aggIsTemporaryUnsupportedDescription": "当前不支持 {modelType} 聚合。", "visTypeTimeseries.unsupportedSplit.splitIsUnsupportedDescription": "不支持拆分依据 {modelType}。", - "visTypeTimeseries.validateInterval.notifier.maxBucketsExceededErrorMessage": "超过最大桶数:{buckets} 大于 {maxBuckets},请在面板选项中尝试较大的时间间隔。", "visTypeTimeseries.vars.variableNameAriaLabel": "变量名称", "visTypeTimeseries.vars.variableNamePlaceholder": "变量名称", "visTypeTimeseries.visEditorVisualization.applyChangesLabel": "应用更改", diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_duration/monitor_duration.tsx b/x-pack/plugins/uptime/public/components/monitor/monitor_duration/monitor_duration.tsx index af1c8dbdc49e..7ecf87fb405d 100644 --- a/x-pack/plugins/uptime/public/components/monitor/monitor_duration/monitor_duration.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/monitor_duration/monitor_duration.tsx @@ -33,7 +33,7 @@ export const MonitorDurationComponent = ({ }: DurationChartProps) => { return ( - +

diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/__snapshots__/monitor_list.test.tsx.snap b/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/__snapshots__/monitor_list.test.tsx.snap index 51f113db5584..5298979be18b 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/__snapshots__/monitor_list.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/__snapshots__/monitor_list.test.tsx.snap @@ -533,35 +533,55 @@ exports[`MonitorList component renders loading state 1`] = ` `; exports[`MonitorList component renders the monitor list 1`] = ` -.c2 { +.c3 { padding-left: 17px; } -.c4 { +.c5 { padding-top: 12px; } -.c0 { +.c1 { margin-left: auto; } -.c3 { +.c4 { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } @media (max-width:574px) { - .c1 { + .c2 { min-width: 230px; } } +@media only screen and (max-width:768px) { + .c0.c0 > :first-child { + -webkit-flex-basis: 40% !important; + -ms-flex-preferred-size: 40% !important; + flex-basis: 40% !important; + } + + .c0.c0 > :nth-child(2) { + -webkit-order: 3; + -ms-flex-order: 3; + order: 3; + } + + .c0.c0 > :nth-child(3) { + -webkit-flex-basis: 60% !important; + -ms-flex-preferred-size: 60% !important; + flex-basis: 60% !important; + } +} +