diff --git a/.bazeliskversion b/.bazeliskversion index 4dae2985b58cc..1cac385c6cb86 100644 --- a/.bazeliskversion +++ b/.bazeliskversion @@ -1 +1 @@ -1.10.1 +1.11.0 diff --git a/.buildkite/scripts/steps/cloud/purge.js b/.buildkite/scripts/steps/cloud/purge.js index 0eccb55cef830..336f7daf736ae 100644 --- a/.buildkite/scripts/steps/cloud/purge.js +++ b/.buildkite/scripts/steps/cloud/purge.js @@ -26,7 +26,7 @@ for (const deployment of prDeployments) { const lastCommit = pullRequest.commits.slice(-1)[0]; const lastCommitTimestamp = new Date(lastCommit.committedDate).getTime() / 1000; - if (pullRequest.state !== 'open') { + if (pullRequest.state !== 'OPEN') { console.log(`Pull Request #${prNumber} is no longer open, will delete associated deployment`); deploymentsToPurge.push(deployment); } else if (!pullRequest.labels.filter((label) => label.name === 'ci:deploy-cloud')) { @@ -50,6 +50,9 @@ for (const deployment of deploymentsToPurge) { console.log(`Scheduling deployment for deletion: ${deployment.name} / ${deployment.id}`); try { execSync(`ecctl deployment shutdown --force '${deployment.id}'`, { stdio: 'inherit' }); + execSync(`vault delete secret/kibana-issues/dev/cloud-deploy/${deployment.name}`, { + stdio: 'inherit', + }); } catch (ex) { console.error(ex.toString()); } diff --git a/.buildkite/scripts/steps/code_coverage/oss_cigroup.sh b/.buildkite/scripts/steps/code_coverage/oss_cigroup.sh index 44d4bb500e1d2..7a54e770b8a3e 100755 --- a/.buildkite/scripts/steps/code_coverage/oss_cigroup.sh +++ b/.buildkite/scripts/steps/code_coverage/oss_cigroup.sh @@ -20,10 +20,19 @@ export CODE_COVERAGE=1 echo "--- OSS CI Group $CI_GROUP" echo " -> Running Functional tests with code coverage" -node scripts/functional_tests \ +NODE_OPTIONS=--max_old_space_size=14336 \ + ./node_modules/.bin/nyc \ + --nycrc-path src/dev/code_coverage/nyc_config/nyc.server.config.js \ + node scripts/functional_tests \ --include-tag "ciGroup$CI_GROUP" \ --exclude-tag "skipCoverage" || true +if [[ -d "$KIBANA_DIR/target/kibana-coverage/server" ]]; then + echo "--- Server side code coverage collected" + mkdir -p target/kibana-coverage/functional + mv target/kibana-coverage/server/coverage-final.json "target/kibana-coverage/functional/oss-${CI_GROUP}-server-coverage.json" +fi + if [[ -d "$KIBANA_DIR/target/kibana-coverage/functional" ]]; then echo "--- Merging code coverage for CI Group $CI_GROUP" yarn nyc report --nycrc-path src/dev/code_coverage/nyc_config/nyc.functional.config.js --reporter json diff --git a/.buildkite/scripts/steps/code_coverage/xpack_cigroup.sh b/.buildkite/scripts/steps/code_coverage/xpack_cigroup.sh index c85191e4e4632..a3fdff6690485 100755 --- a/.buildkite/scripts/steps/code_coverage/xpack_cigroup.sh +++ b/.buildkite/scripts/steps/code_coverage/xpack_cigroup.sh @@ -22,12 +22,21 @@ echo " -> Running X-Pack functional tests with code coverage" cd "$XPACK_DIR" -node scripts/functional_tests \ +NODE_OPTIONS=--max_old_space_size=14336 \ + ./../node_modules/.bin/nyc \ + --nycrc-path ./../src/dev/code_coverage/nyc_config/nyc.server.config.js \ + node scripts/functional_tests \ --include-tag "ciGroup$CI_GROUP" \ --exclude-tag "skipCoverage" || true cd "$KIBANA_DIR" +if [[ -d "$KIBANA_DIR/target/kibana-coverage/server" ]]; then + echo "--- Server side code coverage collected" + mkdir -p target/kibana-coverage/functional + mv target/kibana-coverage/server/coverage-final.json "target/kibana-coverage/functional/xpack-${CI_GROUP}-server-coverage.json" +fi + if [[ -d "$KIBANA_DIR/target/kibana-coverage/functional" ]]; then echo "--- Merging code coverage for CI Group $CI_GROUP" yarn nyc report --nycrc-path src/dev/code_coverage/nyc_config/nyc.functional.config.js --reporter json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 604179ec75706..a8619643d1b2e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -138,13 +138,11 @@ # Uptime /x-pack/plugins/uptime @elastic/uptime /x-pack/plugins/ux @elastic/uptime -/x-pack/plugins/observability/public/components/shared/exploratory_view @elastic/uptime /x-pack/test/functional_with_es_ssl/apps/uptime @elastic/uptime /x-pack/test/functional/apps/uptime @elastic/uptime /x-pack/test/functional/es_archives/uptime @elastic/uptime /x-pack/test/functional/services/uptime @elastic/uptime /x-pack/test/api_integration/apis/uptime @elastic/uptime -/x-pack/plugins/observability/public/components/shared/exploratory_view @elastic/uptime # Client Side Monitoring / Uptime (lives in APM directories but owned by Uptime) /x-pack/plugins/apm/public/application/uxApp.tsx @elastic/uptime @@ -268,6 +266,7 @@ /packages/kbn-std/ @elastic/kibana-core /packages/kbn-config/ @elastic/kibana-core /packages/kbn-logging/ @elastic/kibana-core +/packages/kbn-logging-mocks/ @elastic/kibana-core /packages/kbn-http-tools/ @elastic/kibana-core /src/plugins/saved_objects_management/ @elastic/kibana-core /src/dev/run_check_published_api_changes.ts @elastic/kibana-core diff --git a/.github/ISSUE_TEMPLATE/APM.yml b/.github/ISSUE_TEMPLATE/APM.yml deleted file mode 100644 index cbcabdee25718..0000000000000 --- a/.github/ISSUE_TEMPLATE/APM.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: APM Issue -description: Issues related to the curated APM UI in Kibana -labels: Team:apm -title: "[APM] " -body: - - type: markdown - attributes: - value: | - Thank you for our interest in Elastic APM. This issue tracker is meant for reporting bugs and problems with APM UI. For questions around how to use or setup APM, please refer to our [Discuss Forum](https://discuss.elastic.co/) - - type: input - attributes: - label: Kibana version - validations: - required: true - - type: input - attributes: - label: APM Server version (if applicable) - validations: - required: false - - type: input - attributes: - label: Elasticsearch version (if applicable) - validations: - required: false - - type: textarea - attributes: - label: Steps to Reproduce - description: Steps to reproduce the behavior. - validations: - required: false - - type: textarea - attributes: - label: Expected Behavior - description: A concise description of what you expected to happen. - validations: - required: false - - type: textarea - attributes: - label: Actual Behavior - description: A concise description of what you're experiencing. - validations: - required: false - - diff --git a/.github/workflows/add-to-fleet-project.yml b/.github/workflows/add-to-fleet-project.yml new file mode 100644 index 0000000000000..59b3513c85284 --- /dev/null +++ b/.github/workflows/add-to-fleet-project.yml @@ -0,0 +1,36 @@ +name: Add to Fleet:Quality project +on: + issues: + types: + - labeled +jobs: + add_to_project: + runs-on: ubuntu-latest + if: | + contains(github.event.issue.labels.*.name, 'Team:Fleet') && ( + contains(github.event.issue.labels.*.name, 'technical debt') || + contains(github.event.issue.labels.*.name, 'bug') || + contains(github.event.issue.labels.*.name, 'performance') || + contains(github.event.issue.labels.*.name, 'failed-test') || + contains(github.event.issue.labels.*.name, 'chore') + ) + steps: + - uses: octokit/graphql-action@v2.x + id: add_to_project + with: + headers: '{"GraphQL-Features": "projects_next_graphql"}' + query: | + mutation add_to_project($projectid:String!,$contentid:String!) { + addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { + projectNextItem { + id + } + } + } + projectid: ${{ env.PROJECT_ID }} + contentid: ${{ github.event.issue.node_id }} + env: + # https://github.com/orgs/elastic/projects/763 + PROJECT_ID: "PN_kwDOAGc3Zs4AAsH6" + # Token with `write:org` access + GITHUB_TOKEN: ${{ secrets.FLEET_TECH_KIBANA_USER_TOKEN }} diff --git a/.github/workflows/dev-doc-builder.yml b/.github/workflows/dev-doc-builder.yml deleted file mode 100644 index bbc8745854e48..0000000000000 --- a/.github/workflows/dev-doc-builder.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Dev docs -on: - pull_request_target: - paths: - - '**.mdx' - - '**.docnav.json' - - '**.docapi.json' - - '**.devdocs.json' - - '**.jpg' - - '**.jpeg' - - '**.png' - - '**.gif' - types: [closed, opened, synchronize, reopened] - -jobs: - internal-docs: - uses: elastic/workflows/.github/workflows/dev-docs-builder.yml@main - secrets: - VERCEL_GITHUB_TOKEN: ${{ secrets.VERCEL_GITHUB_TOKEN }} - VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} - VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} - VERCEL_PROJECT_ID_DOCS_DEV: ${{ secrets.VERCEL_PROJECT_ID_DOCS_DEV }} diff --git a/.github/workflows/label-qa-fixed-in.yml b/.github/workflows/label-qa-fixed-in.yml new file mode 100644 index 0000000000000..e1dafa061f623 --- /dev/null +++ b/.github/workflows/label-qa-fixed-in.yml @@ -0,0 +1,78 @@ +name: Add QA labels to Fleet issues +on: + pull_request: + types: + - closed + +jobs: + fetch_issues_to_label: + runs-on: ubuntu-latest + # Only run on PRs that were merged for the Fleet team + if: | + github.event.pull_request.merged_at && + contains(github.event.pull_request.labels.*.name, 'Team:Fleet') + outputs: + matrix: ${{ steps.issues_to_label.outputs.value }} + label_ids: ${{ steps.label_ids.outputs.value }} + steps: + - uses: octokit/graphql-action@v2.x + id: closing_issues + with: + query: | + query closingIssueNumbersQuery($prnumber: Int!) { + repository(owner: "elastic", name: "kibana") { + pullRequest(number: $prnumber) { + closingIssuesReferences(first: 10) { + nodes { + id + labels(first: 20) { + nodes { + id + name + } + } + } + } + } + } + } + prnumber: ${{ github.event.number }} + token: ${{ secrets.GITHUB_TOKEN }} + - uses: sergeysova/jq-action@v2 + id: issues_to_label + with: + # Map to the issues' node id + cmd: echo $CLOSING_ISSUES | jq -c '.repository.pullRequest.closingIssuesReferences.nodes | map(.id)' + multiline: true + env: + CLOSING_ISSUES: ${{ steps.closing_issues.outputs.data }} + - uses: sergeysova/jq-action@v2 + id: label_ids + with: + # Get list of version labels on pull request and map to label's node id, append 'QA:Ready For Testing' id ("MDU6TGFiZWwyNTQ1NjcwOTI4") + cmd: echo $PR_LABELS | jq -c 'map(select(.name | test("v[0-9]+\\.[0-9]+\\.[0-9]+")) | .node_id) + ["MDU6TGFiZWwyNTQ1NjcwOTI4"]' + multiline: true + env: + PR_LABELS: ${{ toJSON(github.event.pull_request.labels) }} + + label_issues: + needs: fetch_issues_to_label + runs-on: ubuntu-latest + # For each issue closed by the PR run this job + strategy: + matrix: + issueNodeId: ${{ fromJSON(needs.fetch_issues_to_label.outputs.matrix) }} + name: Label issue ${{ matrix.issueNodeId }} + steps: + - uses: octokit/graphql-action@v2.x + id: add_labels_to_closed_issue + with: + query: | + mutation add_label($issueid:String!, $labelids:[String!]!) { + addLabelsToLabelable(input: {labelableId: $issueid, labelIds: $labelids}) { + clientMutationId + } + } + issueid: ${{ matrix.issueNodeId }} + labelids: ${{ needs.fetch_issues_to_label.outputs.label_ids }} + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 18302cebd1641..818d3a472d52c 100644 --- a/.gitignore +++ b/.gitignore @@ -66,6 +66,7 @@ npm-debug.log* .ci/bash_standard_lib.sh .gradle .vagrant +.envrc ## @cypress/snapshot from apm plugin /snapshots.js diff --git a/dev_docs/key_concepts/building_blocks.mdx b/dev_docs/key_concepts/building_blocks.mdx index 6536019f668cf..61e3a711775c3 100644 --- a/dev_docs/key_concepts/building_blocks.mdx +++ b/dev_docs/key_concepts/building_blocks.mdx @@ -122,6 +122,12 @@ sharing and space isolation, and tags. **Github labels**: `Team:Core`, `Feature:Saved Objects` +## Advanced Settings + + + - + - + - + +`uiSettings` are registered synchronously during `core`'s setup lifecycle phase. This means that once you add a new advanced setting, you cannot change or remove it without . + +### Configuration with Advanced Settings UI + +The `uiSettings` service is the programmatic interface to Kibana's Advanced Settings UI. Kibana plugins use the service to extend Kibana UI Settings Management with custom settings for a plugin. + +Configuration through the Advanced Settings UI is restricted to users authorised to access the Advanced Settings page. Users who don't have permissions to change these values default to using the csettings configuration defined for the space that they are in. The `config` saved object can be shared between spaces. + +### Configuration with UI settings overrides + +When a setting is configured as an override in `kibana.yml`, it overrides any other value store in the `config` saved object. The override applies to Kibana as a whole for all spaces and users, and the option is disabled on the Advanced Settings page. We refer to these as "global" overrides. + +Note: If an override is misconfigured, it fails config validation and prevents Kibana from starting up. Validation is, however, limited to value _type_ and not to _key_ (name). For example, when a plugin registers the `my_plugin_foo: 42` setting , then declares the following override, the config validation fails: + +```kibana.yml +uiSettings.overrides: + my_plugin_foo: "42" +``` +The following override results in a successful config validation: + +```kibana.yml +uiSettings.overrides: + my_pluginFoo: 42 +``` + +### Client side usage + +On the client, the `uiSettings` service is accessible directly from `core` and the client provides plugins access to the `config` entries stored in Elasticsearch. + + + Refer to [the client-side uiSettings service API docs](https://github.com/elastic/kibana/blob/main/docs/development/core/server/kibana-plugin-core-public.iuisettingsclient.md) + + +The following is a basic example for using the `uiSettings` service: + +**src/plugins/charts/public/plugin.ts** +```ts +import { Plugin, CoreSetup } from 'kibana/public'; +import { ExpressionsSetup } from '../../expressions/public'; +import { palette, systemPalette } from '../common'; + +import { ThemeService, LegacyColorsService } from './services'; +import { PaletteService } from './services/palettes/service'; +import { ActiveCursor } from './services/active_cursor'; + +export type Theme = Omit; +export type Color = Omit; + +interface SetupDependencies { + expressions: ExpressionsSetup; +} + +/** @public */ +export interface ChartsPluginSetup { + legacyColors: Color; + theme: Theme; + palettes: ReturnType; +} + +/** @public */ +export type ChartsPluginStart = ChartsPluginSetup & { + activeCursor: ActiveCursor; +}; + +/** @public */ +export class ChartsPlugin implements Plugin { + private readonly themeService = new ThemeService(); + private readonly legacyColorsService = new LegacyColorsService(); + private readonly paletteService = new PaletteService(); + private readonly activeCursor = new ActiveCursor(); + + private palettes: undefined | ReturnType; + + public setup(core: CoreSetup, dependencies: SetupDependencies): ChartsPluginSetup { + dependencies.expressions.registerFunction(palette); + dependencies.expressions.registerFunction(systemPalette); + this.themeService.init(core.uiSettings); + this.legacyColorsService.init(core.uiSettings); + this.palettes = this.paletteService.setup(this.legacyColorsService); + + this.activeCursor.setup(); + + return { + legacyColors: this.legacyColorsService, + theme: this.themeService, + palettes: this.palettes, + }; + } + + public start(): ChartsPluginStart { + return { + legacyColors: this.legacyColorsService, + theme: this.themeService, + palettes: this.palettes!, + activeCursor: this.activeCursor, + }; + } +} + +``` + +### Server side usage + +On the server side, `uiSettings` are accessible directly from `core`. The following example shows how to register a new setting with the minimum required schema parameter against which validations are performed on read and write. +The example also shows how plugins can leverage the optional deprecation parameter on registration for handling deprecation notices and renames. The deprecation warnings are rendered in the Advanced Settings UI and should also be added to the Configure Kibana guide. + + + Refer to [the server-side uiSettings service API docs](https://github.com/elastic/kibana/blob/main/docs/development/core/server/kibana-plugin-core-server.iuisettingsclient.md) + + +**src/plugins/charts/server/plugin.ts** + +```ts +import { i18n } from '@kbn/i18n'; +import { schema } from '@kbn/config-schema'; +import { CoreSetup, Plugin } from 'kibana/server'; +import { COLOR_MAPPING_SETTING, LEGACY_TIME_AXIS, palette, systemPalette } from '../common'; +import { ExpressionsServerSetup } from '../../expressions/server'; + +interface SetupDependencies { + expressions: ExpressionsServerSetup; +} + +export class ChartsServerPlugin implements Plugin { + public setup(core: CoreSetup, dependencies: SetupDependencies) { + dependencies.expressions.registerFunction(palette); + dependencies.expressions.registerFunction(systemPalette); + core.uiSettings.register({ + [COLOR_MAPPING_SETTING]: { + name: i18n.translate('charts.advancedSettings.visualization.colorMappingTitle', { + defaultMessage: 'Color mapping', + }), + value: JSON.stringify({ + Count: '#00A69B', + }), + type: 'json', + description: i18n.translate('charts.advancedSettings.visualization.colorMappingText', { + defaultMessage: + 'Maps values to specific colors in charts using the Compatibility palette.', + }), + deprecation: { + message: i18n.translate( + 'charts.advancedSettings.visualization.colorMappingTextDeprecation', + { + defaultMessage: + 'This setting is deprecated and will not be supported in a future version.', + } + ), + docLinksKey: 'visualizationSettings', + }, + category: ['visualization'], + schema: schema.string(), + }, + ... + }); + + return {}; + } + + public start() { + return {}; + } + + public stop() {} +} +``` +For optimal Kibana performance, `uiSettings` are cached. Any changes that require a cache refresh should use the `requiresPageReload` parameter on registration. + +For example, changing the time filter refresh interval triggers a prompt in the UI that the page needs to be refreshed to save the new value: + +**src/plugins/data/server/ui_settings.ts** + +```ts +import { i18n } from '@kbn/i18n'; +import { schema } from '@kbn/config-schema'; +import type { DocLinksServiceSetup, UiSettingsParams } from 'kibana/server'; +import { DEFAULT_QUERY_LANGUAGE, UI_SETTINGS } from '../common'; + +export function getUiSettings( + docLinks: DocLinksServiceSetup +): Record> { + return { + ... + [UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS]: { + name: i18n.translate('data.advancedSettings.timepicker.refreshIntervalDefaultsTitle', { + defaultMessage: 'Time filter refresh interval', + }), + value: `{ + "pause": false, + "value": 0 + }`, + type: 'json', + description: i18n.translate('data.advancedSettings.timepicker.refreshIntervalDefaultsText', { + defaultMessage: `The timefilter's default refresh interval. The "value" needs to be specified in milliseconds.`, + }), + requiresPageReload: true, + schema: schema.object({ + pause: schema.boolean(), + value: schema.number(), + }), + }, + ... + } +} +``` + +### Registering Migrations +To change or remove a `uiSetting`, you must migrate the whole `config` Saved Object. `uiSettings` migrations are declared directly in the service. + +For example, in 7.9.0, `siem` as renamed to `securitySolution`, and in 8.0.0, `theme:version` was removed: + +**src/core/server/ui_settings/saved_objects/migrations.ts** + +```ts +import { SavedObjectUnsanitizedDoc, SavedObjectSanitizedDoc } from 'kibana/server'; + +export const migrations = { + '7.9.0': (doc: SavedObjectUnsanitizedDoc): SavedObjectSanitizedDoc => ({ + ...doc, + ...(doc.attributes && { + attributes: Object.keys(doc.attributes).reduce( + (acc, key) => + key.startsWith('siem:') + ? { + ...acc, + [key.replace('siem', 'securitySolution')]: doc.attributes[key], + } + : { + ...acc, + [key]: doc.attributes[key], + }, + {} + ), + }), + references: doc.references || [], + }), + '7.12.0': (doc: SavedObjectUnsanitizedDoc): SavedObjectSanitizedDoc => ({...}), + '7.13.0': (doc: SavedObjectUnsanitizedDoc): SavedObjectSanitizedDoc => ({...}), + '8.0.0': (doc: SavedObjectUnsanitizedDoc): SavedObjectSanitizedDoc => ({ + ...doc, + ...(doc.attributes && { + attributes: Object.keys(doc.attributes).reduce( + (acc, key) => + [ + // owner: Team:Geo [1] + 'visualization:regionmap:showWarnings', + ... + // owner: Team:Core + ... + 'theme:version', + // owner: Team:AppServices + ... + ].includes(key) + ? { + ...acc, + } + : { + ...acc, + [key]: doc.attributes[key], + }, + {} + ), + }), + references: doc.references || [], + }), + '8.1.0': (doc: SavedObjectUnsanitizedDoc): SavedObjectSanitizedDoc => ({...}), +}; +``` +[1] Since all `uiSettings` migrations are added to the same migration function, while not required, grouping settings by team is good practice. diff --git a/docs/CHANGELOG.asciidoc b/docs/CHANGELOG.asciidoc index 446c305c03b95..47ebcd573edc2 100644 --- a/docs/CHANGELOG.asciidoc +++ b/docs/CHANGELOG.asciidoc @@ -10,6 +10,7 @@ Review important information about the {kib} 8.0.0 releases. +* <> * <> * <> * <> @@ -17,6 +18,139 @@ Review important information about the {kib} 8.0.0 releases. * <> -- +[[release-notes-8.0.0]] +== {kib} 8.0.0 + +Review the {kib} 8.0.0 changes, then use the {kibana-ref-all}/7.17/upgrade-assistant.html[Upgrade Assistant] to complete the upgrade. + +[float] +[[breaking-changes-8.0.0]] +=== Breaking change + +Breaking changes can prevent your application from optimal operation and performance. +Before you upgrade to 8.0.0, review the breaking change, then mitigate the impact to your application. + +// tag::notable-breaking-changes[] + +[discrete] +[[breaking-123754]] +.Removes the `console.ssl` setting +[%collapsible] +==== +*Details* + +The `console.ssl` setting has been removed. For more information, refer to {kibana-pull}123754[#123754]. + +*Impact* + +Before you upgrade to 8.0.0, remove `console.ssl` from kibana.yml. +==== + +// end::notable-breaking-changes[] + + +To review the breaking changes in previous versions, refer to the following: + +<> | <> | <> | <> | +<> + +[float] +[[deprecations-8.0.0]] +=== Deprecation + +The following functionality is deprecated in 8.0.0, and will be removed in 9.0.0. +Deprecated functionality does not have an immediate impact on your application, but we strongly recommend +you make the necessary updates after you upgrade to 8.0.0. + +[discrete] +[[deprecation-123229]] +.Removes support for `monitoring.cluster_alerts.allowedSpaces` +[%collapsible] +==== +*Details* + +The `monitoring.cluster_alerts.allowedSpaces` setting, which {kib} uses to create Stack Monitoring alerts, has been removed. For more information, refer to {kibana-pull}123229[#123229]. + +*Impact* + +Before you upgrade to 8.0.0, remove `monitoring.cluster_alerts.allowedSpaces` from kibana.yml. +==== + +To review the deprecations in previous versions, refer to the following: + +<> | <> + +[float] +[[known-issue-8.0.0]] +=== Known issue + +[discrete] +[[known-issue-123550]] +.Importing and copying saved objects causes weak links to break +[%collapsible] +==== +*Details* + +{kib} supports weak links in some saved objects. For example, a dashboard may include a Markdown panel that contains a relative URL to +another dashboard. Weak links are defined by free text, _not_ the saved object's relationships, and can break if **both** of the following +conditions are true: + +* You are importing saved objects into multiple spaces, _OR_ you are copying saved objects into another space +* Before you upgraded to 8.0.0, the saved objects did not already exist in the destinations + +In 8.0.0 and later, weak links break because <>. +This applies to both the UI and the API. +This issue will be fixed 8.0.1 and 8.1.0. For more information, refer to {kibana-issue}123550[#123550]. + +*Impact* + +Saved objects in 7.x that are migrated during upgrade are **not** impacted. +Only _new_ saved objects that are imported or copied _multiple times_ (causing object IDs to change) are impacted. +If you are impacted, you can re-import or re-copy your saved objects after the fix is +implemented to preserve the weak links. +==== + +[float] +[[features-8.0.0]] +=== Features +For information about the features introduced in 8.0.0, refer to <>. + +Elastic Security:: +For the Elastic Security 8.0.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. + +To review the features in previous versions, refer to the following: + +<> | <> | <> | <> + +[[enhancements-and-bug-fixes-v8.0.0]] +=== Enhancements and bug fixes + +For detailed information about the 8.0.0 release, review the enhancements and bug fixes. + +[float] +[[enhancement-v8.0.0]] +==== Enhancements +Dashboard:: +Clone ReferenceOrValueEmbeddables by value {kibana-pull}122199[#122199] + +Elastic Security:: +For the Elastic Security 8.0.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. + +[float] +[[fixes-v8.0.0]] +==== Bug Fixes +APM:: +Restrict aggregated transaction metrics search to date range {kibana-pull}123445[#123445] + +Elastic Security:: +For the Elastic Security 8.0.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. + +Fleet:: +Allow empty strings for required text fields in package policies {kibana-pull}123610[#123610] + +Maps:: +Fixes Label border color is not removed from legend when disabled {kibana-pull}122705[#122705] + +Monitoring:: +Ensure logstash getNodes always contains a uuid {kibana-pull}124201[#124201] + +Security:: +Long-running requests no longer cause sporadic logouts in certain cases, even when user sessions are active {kibana-pull}122155[#122155] + [[release-notes-8.0.0-rc2]] == {kib} 8.0.0-rc2 @@ -29,8 +163,6 @@ For information about the {kib} 8.0.0-rc2 release, review the following informat Breaking changes can prevent your application from optimal operation and performance. Before you upgrade, review the breaking change, then mitigate the impact to your application. -// tag::notable-breaking-changes[] - [discrete] [[breaking-122722]] .Removes the ability to use `elasticsearch.username: elastic` in production @@ -42,14 +174,12 @@ In production, you are no longer able to use the `elastic` superuser to authenti *Impact* + When you configure `elasticsearch.username: elastic`, {kib} fails. ==== - -// end::notable-breaking-changes[] To review the breaking changes in previous versions, refer to the following: <> | <> | <> | <> - + [float] [[features-8.0.0-rc2]] === Features @@ -1304,7 +1434,7 @@ Use the `xpack.monitoring.clusterAlertsEmail` in kibana.yml. ==== [float] -[[enhancements-and-bug-fixes-v8.0.0]] +[[enhancements-and-bug-fixes-v8.0.0-alpha1]] === Bug fix The 8.0.0-alpha1 release includes the following bug fix. diff --git a/docs/developer/advanced/sharing-saved-objects.asciidoc b/docs/developer/advanced/sharing-saved-objects.asciidoc index 9a009e6ad49b7..beff7cc007b6d 100644 --- a/docs/developer/advanced/sharing-saved-objects.asciidoc +++ b/docs/developer/advanced/sharing-saved-objects.asciidoc @@ -405,8 +405,8 @@ necessary. However, such handling of secondary objects is not considered critica ==== 4. What is a "legacy URL alias"? As depicted above, when an object is converted to become share-capable, if it exists in a non-Default space, its ID gets changed. To -preserve its old ID, we also create a special object called a _legacy URL alias_ ("alias" for short); this alias retains the target object's -old ID (_sourceId_), and it contains a pointer to the target object's new ID (_targetId_). +preserve its old ID, we also create a special object called a <> ("alias" for short); this alias +retains the target object's old ID (_sourceId_), and it contains a pointer to the target object's new ID (_targetId_). Aliases are meant to be mostly invisible to end-users by design. There is no UI to manage them directly. Our vision is that aliases will be used as a stop-gap to help us through the 8.0 upgrade process, but we will nudge users away from relying on aliases so we can eventually @@ -473,3 +473,7 @@ change any other data flows to use `resolve()`. External plugins (those not shipped with {kib}) can use this guide to convert any isolated objects to become share-capable or fully shareable! If you are an external plugin developer, the steps are the same, but you don't need to worry about getting anything done before a specific release. The only thing you need to know is that your plugin cannot convert your objects until the 8.0 release. + +==== 8. How will users be impacted? + +Refer to <> documentation for more details how users should expect to be impacted. diff --git a/docs/developer/architecture/core/index.asciidoc b/docs/developer/architecture/core/index.asciidoc index 53720a593d3f2..6c70205c3590d 100644 --- a/docs/developer/architecture/core/index.asciidoc +++ b/docs/developer/architecture/core/index.asciidoc @@ -36,9 +36,3 @@ The services that core provides are: * <> * <> * <> - - - - - - diff --git a/docs/setup/upgrade/logging-configuration-changes.asciidoc b/docs/developer/architecture/core/logging-configuration-migration.asciidoc similarity index 89% rename from docs/setup/upgrade/logging-configuration-changes.asciidoc rename to docs/developer/architecture/core/logging-configuration-migration.asciidoc index 4d5f5f732536e..4a9d03d3b5312 100644 --- a/docs/setup/upgrade/logging-configuration-changes.asciidoc +++ b/docs/developer/architecture/core/logging-configuration-migration.asciidoc @@ -2,7 +2,7 @@ [[logging-config-changes]] === Logging configuration changes -WARNING: {kib} 8.0 and later uses a new logging system. Be sure to read the documentation for your version of {kib} before proceeding. +WARNING: {kib} 8.0.0 and later uses a new logging system. Before you upgrade, read the documentation for your {kib} version. [[logging-pattern-format-old-and-new-example]] [options="header"] diff --git a/docs/developer/architecture/development-visualize-index.asciidoc b/docs/developer/architecture/development-visualize-index.asciidoc index d41ee32c1fb27..b941cdedf9df9 100644 --- a/docs/developer/architecture/development-visualize-index.asciidoc +++ b/docs/developer/architecture/development-visualize-index.asciidoc @@ -19,7 +19,7 @@ We would recommend waiting until later in `7.x` to upgrade your plugins if possi If you would like to keep up with progress on the visualizations plugin in the meantime, here are a few resources: -* The <> documentation, where we try to capture any changes to the APIs as they occur across minors. +* The <> documentation, where we try to capture any changes to the APIs as they occur across minors. * link:https://github.com/elastic/kibana/issues/44121[Meta issue] which is tracking the move of the plugin to the new {kib} platform * Our link:https://www.elastic.co/blog/join-our-elastic-stack-workspace-on-slack[Elastic Stack workspace on Slack]. * The {kib-repo}blob/{branch}/src/plugins/visualizations[source code], which will continue to be diff --git a/docs/developer/architecture/index.asciidoc b/docs/developer/architecture/index.asciidoc index 774292f513f03..90a0972d65f2f 100644 --- a/docs/developer/architecture/index.asciidoc +++ b/docs/developer/architecture/index.asciidoc @@ -40,6 +40,8 @@ include::core/http-service.asciidoc[leveloffset=+1] include::core/logging-service.asciidoc[leveloffset=+1] +include::core/logging-configuration-migration.asciidoc[leveloffset=+1] + include::core/saved-objects-service.asciidoc[leveloffset=+1] include::core/uisettings-service.asciidoc[leveloffset=+1] diff --git a/docs/development/core/public/kibana-plugin-core-public.iexternalurl.isinternalurl.md b/docs/development/core/public/kibana-plugin-core-public.iexternalurl.isinternalurl.md new file mode 100644 index 0000000000000..396e5586f1fed --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.iexternalurl.isinternalurl.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [IExternalUrl](./kibana-plugin-core-public.iexternalurl.md) > [isInternalUrl](./kibana-plugin-core-public.iexternalurl.isinternalurl.md) + +## IExternalUrl.isInternalUrl() method + +Determines if the provided URL is an internal url. + +Signature: + +```typescript +isInternalUrl(relativeOrAbsoluteUrl: string): boolean; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| relativeOrAbsoluteUrl | string | | + +Returns: + +boolean + diff --git a/docs/development/core/public/kibana-plugin-core-public.iexternalurl.md b/docs/development/core/public/kibana-plugin-core-public.iexternalurl.md index 5a598281c7be7..d0d4e6a3a4464 100644 --- a/docs/development/core/public/kibana-plugin-core-public.iexternalurl.md +++ b/docs/development/core/public/kibana-plugin-core-public.iexternalurl.md @@ -16,5 +16,6 @@ export interface IExternalUrl | Method | Description | | --- | --- | +| [isInternalUrl(relativeOrAbsoluteUrl)](./kibana-plugin-core-public.iexternalurl.isinternalurl.md) | Determines if the provided URL is an internal url. | | [validateUrl(relativeOrAbsoluteUrl)](./kibana-plugin-core-public.iexternalurl.validateurl.md) | Determines if the provided URL is a valid location to send users. Validation is based on the configured allow list in kibana.yml.If the URL is valid, then a URL will be returned. Otherwise, this will return null. | diff --git a/docs/index.asciidoc b/docs/index.asciidoc index ec1a99fa5bffc..668a6edcad3db 100644 --- a/docs/index.asciidoc +++ b/docs/index.asciidoc @@ -24,8 +24,6 @@ include::user/index.asciidoc[] include::accessibility.asciidoc[] -include::migration.asciidoc[] - include::CHANGELOG.asciidoc[] include::developer/index.asciidoc[] diff --git a/docs/management/connectors/pre-configured-connectors.asciidoc b/docs/management/connectors/pre-configured-connectors.asciidoc index 4d304cdd6c5a2..aaef1b673d0b6 100644 --- a/docs/management/connectors/pre-configured-connectors.asciidoc +++ b/docs/management/connectors/pre-configured-connectors.asciidoc @@ -11,6 +11,8 @@ action are predefined, including the connector name and ID. - Appear in all spaces because they are not saved objects. - Cannot be edited or deleted. +NOTE: Preconfigured connectors cannot be used with cases. + [float] [[preconfigured-connector-example]] ==== Preconfigured connectors example @@ -70,4 +72,4 @@ image::images/pre-configured-connectors-managing.png[Connectors managing tab wit Clicking a preconfigured connector shows the description, but not the configuration. A message indicates that this is a preconfigured connector. [role="screenshot"] -image::images/pre-configured-connectors-view-screen.png[Pre-configured connector view details] \ No newline at end of file +image::images/pre-configured-connectors-view-screen.png[Pre-configured connector view details] diff --git a/docs/management/managing-saved-objects.asciidoc b/docs/management/managing-saved-objects.asciidoc index b9859575051af..3901d178e1461 100644 --- a/docs/management/managing-saved-objects.asciidoc +++ b/docs/management/managing-saved-objects.asciidoc @@ -126,3 +126,5 @@ WARNING: Validation is not performed for object properties. Submitting an invali change will render the object unusable. A more failsafe approach is to use *Discover* or *Dashboard* to create new objects instead of directly editing an existing one. + +include::saved-objects/saved-object-ids.asciidoc[] diff --git a/docs/management/saved-objects/saved-object-ids.asciidoc b/docs/management/saved-objects/saved-object-ids.asciidoc new file mode 100644 index 0000000000000..a09f91ee361cd --- /dev/null +++ b/docs/management/saved-objects/saved-object-ids.asciidoc @@ -0,0 +1,87 @@ +[[saved-object-ids]] +=== Saved Object IDs + +In the past, many saved object types could have the same ID in different <>. For example, if you copied dashboard "123" +from the one space to another space, the second dashboard would also have an ID of "123". While the saved object ID is not something +that users would interact with directly, many aspects of {kib} rely on it, notably URLs. If you have a "deep link" URL to a saved dashboard, +that URL includes the saved object ID. + +**Starting in the 8.0 release**, {kib} requires most saved objects to have _globally unique_ IDs. This is a change that we needed to make to +support sharing saved objects to multiple spaces. Most saved objects cannot be shared to multiple spaces _yet_, but we needed to start +enforcing globally unique object IDs first. + +We have made several enhancements to minimize the impact, and this document describes what you need to know about the changes and +how it will affect you. + +[[saved-object-ids-impact-upon-upgrading]] +==== Impact upon upgrading to 8.x + +Every time you upgrade {kib}, <>. When you +first upgrade from 7.x to 8.x, this migration process will start enforcing globally unique saved object IDs. + +In practical terms, **any old saved objects that exist in a custom space will have their IDs changed to a new UUID**, while saved objects in +the Default space will be unchanged. This is how we can ensure that every saved object ID is unique. For example: if you had dashboard "123" +in the Default space and dashboard "123" in Another space, after the upgrade you would have dashboard "123" in the Default space and +dashboard "456" in Another space. + +[[saved-object-ids-impact-when-using]] +==== Impact when using 8.x + +After you upgrade, or if you set up a new {kib} instance using 8.x, there are a few more things that behave differently. + +[[saved-object-ids-impact-when-using-legacy-urls]] +===== Accessing saved objects using old URLs + +When you upgrade {kib} and saved object IDs change, the "deep link" URLs to access those saved objects will also change. To reduce the impact, +each existing URL is preserved with a special <>. This means that if you use a bookmark for +a saved object ID that was changed, you'll be redirected to the new URL for that saved object. + +[[saved-object-ids-impact-when-using-import-and-copy]] +===== Importing and copying saved objects + +When you <>, {kib} effectively +<>. In this way, copying a saved object has always behaved +like an import. In this document when we say "import", it applies to both features. + +Historically, whether you imported or copied a saved object, {kib} would create _at most_ one copy of a saved object in that space. If you +imported the saved object multiple times, {kib} would overwrite the existing object, because it used the same ID. Since saved object IDs are +now globally unique, {kib} maintains this functionality by tracking each saved object's _origin_. When you import an object in 8.x, {kib} +uses either the saved object ID _or_ the origin to determine its destination. + +If you import a saved object using the "Check for existing objects" option -- whether it was exported from 7.x or 8.x -- {kib} will +take the following steps: + +1. If {kib} finds a matching saved object with the exact same ID in the target space, that will be the import destination -- you can **overwrite** that +destination or **skip** it. + +2. Otherwise, if {kib} finds a matching saved object with a _different_ ID that has the same origin, that will be the import destination +-- again, you can **overwrite** that destination or **skip** it. + +3. Otherwise, if a saved object with the exact same ID exists in a _different_ space, then {kib} will generate a random ID for the import +destination, preserving the saved object's origin. + +4. Otherwise, {kib} creates the saved object with the given ID. + +For example, you have a saved object in an `export.ndjson` file, and you set up a brand new {kib} instance. You attempt to import the saved +object using the "Check for existing objects" and "Automatically overwrite conflicts" options. The first time you import the saved object, +{kib} will create a new object with the same ID (step 4 above). If you import it again, {kib} will find that object and overwrite it (step 1 +above). If you then create a _different_ space and import it there, {kib} will create a new object with a random ID (step 3 above). Finally, +if you import it into the second space again, {kib} will find the second object with a matching origin and overwrite it (step 2 above). + +WARNING: When you import a saved object and it is created with a different ID, if 1. it contains weak links to other saved objects (such as +a dashboard with a Markdown URL to navigate to another dashboard) and 2. the object's ID has changed (step 3 above), those weak links will +be broken. For more information, refer to <>. + +[[saved-object-ids-impact-when-using-apis]] +===== Using the saved objects APIs + +If you are using the saved objects APIs directly, you should be aware of these changes: + +* When using the <> or <> API, you may encounter + <> that **cannot** be overridden using the `overwrite: true` + option. This can occur if there is already a saved object with this ID in a _different_ space, or if there is a legacy URL alias for this + ID in the same space. +* When using the <> or <> API, objects can potentially be + created with a different ID as described above. +* When using the <> API, if the saved object exists in multiple spaces, it can only be deleted by using the + <>. diff --git a/docs/management/upgrade-assistant/index.asciidoc b/docs/management/upgrade-assistant/index.asciidoc deleted file mode 100644 index ccd3f41b9d886..0000000000000 --- a/docs/management/upgrade-assistant/index.asciidoc +++ /dev/null @@ -1,26 +0,0 @@ -[role="xpack"] -[[upgrade-assistant]] -== Upgrade Assistant - -The Upgrade Assistant helps you prepare for your upgrade -to the next major version of the Elastic Stack. -To access the assistant, open the main menu and go to *Stack Management > Upgrade Assistant*. - -The assistant identifies deprecated settings in your configuration, -enables you to see if you are using deprecated features, -and guides you through the process of resolving issues. - -If you have indices that were created prior to 7.0, -you can use the assistant to reindex them so they can be accessed from 8.0+. - -IMPORTANT: To see the most up-to-date deprecation information before -upgrading to 8.0, upgrade to the latest {prev-major-last} release. - -For more information about upgrading, -refer to {stack-ref}/upgrading-elastic-stack.html[Upgrading to Elastic {version}.] - -[discrete] -=== Required permissions - -The `manage` cluster privilege is required to access the *Upgrade assistant*. -Additional privileges may be needed to perform certain actions. \ No newline at end of file diff --git a/docs/migration/migrate_8_0.asciidoc b/docs/migration/migrate_8_0.asciidoc index 8936e41762c69..18be9a9364c22 100644 --- a/docs/migration/migrate_8_0.asciidoc +++ b/docs/migration/migrate_8_0.asciidoc @@ -7,8 +7,6 @@ This section discusses the changes that you need to be aware of when migrating your application to Kibana 8.0. -coming[8.0.0] - See also <> and <>. * <> diff --git a/docs/redirects.asciidoc b/docs/redirects.asciidoc index ff6ccbd6fab36..2c9d48813781a 100644 --- a/docs/redirects.asciidoc +++ b/docs/redirects.asciidoc @@ -386,12 +386,22 @@ This content has moved. Refer to <>. This content has moved. Refer to <>. -[role="exclude" logging-configuration-changes] -== Logging configuration changes - -This content has moved. Refer to <>. - -[role="exclude" upgrade-migrations] +[role="exclude",id="upgrade-migrations"] == Upgrade migrations This content has moved. Refer to <>. + +[role="exclude",id="upgrade-standard"] +== Standard Upgrade + +This content has moved. Refer to {stack-ref}/upgrading-kibana.html[Upgrade Kibana]. + +[role="exclude",id="upgrade-assistant"] +== Upgrade Assistant + +This content has moved. Refer to {kibana-ref-all}/7.17/upgrade-assistant.html[Upgrade Assistant]. + +[role="exclude",id="brew"] +== Install {kib} on macOS with Homebrew + +This page has been deleted. Refer to <>. diff --git a/docs/settings/apm-settings.asciidoc b/docs/settings/apm-settings.asciidoc index 27ea7f4dc7cd0..7441621f441f9 100644 --- a/docs/settings/apm-settings.asciidoc +++ b/docs/settings/apm-settings.asciidoc @@ -74,7 +74,7 @@ Changing these settings may disable features of the APM App. | Index name where Observability annotations are stored. Defaults to `observability-annotations`. | `xpack.apm.searchAggregatedTransactions` {ess-icon} - | experimental[] Enables Transaction histogram metrics. Defaults to `never` and aggregated transactions are not used. When set to `auto`, the UI will use metric indices over transaction indices for transactions if aggregated transactions are found. When set to `always`, additional configuration in APM Server is required. + | Enables Transaction histogram metrics. Defaults to `auto` so the UI will use metric indices over transaction indices for transactions if aggregated transactions are found. When set to `always`, additional configuration in APM Server is required. When set to `never` and aggregated transactions are not used. See {apm-guide-ref}/transaction-metrics.html[Configure transaction metrics] for more information. | `xpack.apm.metricsInterval` {ess-icon} diff --git a/docs/settings/security-settings.asciidoc b/docs/settings/security-settings.asciidoc index 56d08ee24efe1..787efa64f0775 100644 --- a/docs/settings/security-settings.asciidoc +++ b/docs/settings/security-settings.asciidoc @@ -8,10 +8,6 @@ You do not need to configure any additional settings to use the {security-features} in {kib}. They are enabled by default. -[float] -[[general-security-settings]] -==== General security settings - [float] [[authentication-security-settings]] ==== Authentication security settings @@ -46,123 +42,80 @@ xpack.security.authc: <3> Specifies the settings for the SAML authentication provider with a `saml1` name. <4> Specifies the settings for the SAML authentication provider with a `saml2` name. -The valid settings in the `xpack.security.authc.providers` namespace vary depending on the authentication provider type. For more information, refer to <>. - [float] [[authentication-provider-settings]] -===== Valid settings for all authentication providers - -[cols="2*<"] -|=== -| `xpack.security.authc.providers.` -`..enabled` {ess-icon} -| Determines if the authentication provider should be enabled. By default, {kib} enables the provider as soon as you configure any of its properties. - -| `xpack.security.authc.providers.` -`..order` {ess-icon} -| Order of the provider in the authentication chain and on the Login Selector UI. - -| `xpack.security.authc.providers.` -`..description` {ess-icon} -| Custom description of the provider entry displayed on the Login Selector UI. - -| `xpack.security.authc.providers.` -`..hint` {ess-icon} -| Custom hint for the provider entry displayed on the Login Selector UI. - -| `xpack.security.authc.providers.` -`..icon` {ess-icon} -| Custom icon for the provider entry displayed on the Login Selector UI. - -| `xpack.security.authc.providers..` -`.showInSelector` {ess-icon} -| Flag that indicates if the provider should have an entry on the Login Selector UI. Setting this to `false` doesn't remove the provider from the authentication chain. - -2+a| -[TIP] -[NOTE] -============ -You are unable to set this setting to `false` for `basic` and `token` authentication providers. -============ - -| `xpack.security.authc.providers..` -`.accessAgreement.message` {ess-icon} -| Access agreement text in Markdown format. For more information, refer to <>. - -| [[xpack-security-provider-session-idleTimeout]] `xpack.security.authc.providers..` -`.session.idleTimeout` {ess-icon} -| Ensures that user sessions will expire after a period of inactivity. Setting this to `0` will prevent sessions from expiring because of inactivity. By default, this setting is equal to <>. - -2+a| -[TIP] -============ -Use a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). -============ - -| [[xpack-security-provider-session-lifespan]] `xpack.security.authc.providers..` -`.session.lifespan` {ess-icon} -| Ensures that user sessions will expire after the defined time period. This behavior is also known as an "absolute timeout". If -this is set to `0`, user sessions could stay active indefinitely. By default, this setting is equal to <>. +==== Valid settings for all authentication providers + +The valid settings in the `xpack.security.authc.providers` namespace vary depending on the authentication provider type. For more information, refer to <>. + +xpack.security.authc.providers...enabled {ess-icon}:: +Determines if the authentication provider should be enabled. By default, {kib} enables the provider as soon as you configure any of its properties. + +xpack.security.authc.providers...order {ess-icon}:: +Order of the provider in the authentication chain and on the Login Selector UI. + +xpack.security.authc.providers...description {ess-icon}:: +Custom description of the provider entry displayed on the Login Selector UI. -2+a| -[TIP] -============ -Use a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). -============ +xpack.security.authc.providers...hint {ess-icon}:: +Custom hint for the provider entry displayed on the Login Selector UI. -|=== +xpack.security.authc.providers...icon {ess-icon}:: +Custom icon for the provider entry displayed on the Login Selector UI. + +xpack.security.authc.providers...showInSelector {ess-icon}:: +Flag that indicates if the provider should have an entry on the Login Selector UI. Setting this to `false` doesn't remove the provider from the authentication chain. ++ +NOTE: You are unable to set this setting to `false` for `basic` and `token` authentication providers. + +xpack.security.authc.providers...accessAgreement.message {ess-icon}:: +Access agreement text in Markdown format. For more information, refer to <>. + +[[xpack-security-provider-session-idleTimeout]] xpack.security.authc.providers...session.idleTimeout {ess-icon}:: +Ensures that user sessions will expire after a period of inactivity. Setting this to `0` will prevent sessions from expiring because of inactivity. By default, this setting is equal to <>. ++ +NOTE: Use a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). + +[[xpack-security-provider-session-lifespan]] xpack.security.authc.providers...session.lifespan {ess-icon}:: +Ensures that user sessions will expire after the defined time period. This behavior is also known as an "absolute timeout". If +this is set to `0`, user sessions could stay active indefinitely. By default, this setting is equal to <>. ++ +NOTE: Use a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). [float] [[saml-authentication-provider-settings]] -===== SAML authentication provider settings +==== SAML authentication provider settings In addition to <>, you can specify the following settings: -[cols="2*<"] -|=== -| `xpack.security.authc.providers.` -`saml..realm` {ess-icon} -| SAML realm in {es} that provider should use. +xpack.security.authc.providers.saml..realm {ess-icon}:: +SAML realm in {es} that provider should use. -| `xpack.security.authc.providers.` -`saml..useRelayStateDeepLink` {ess-icon} -| Determines if the provider should treat the `RelayState` parameter as a deep link in {kib} during Identity Provider initiated log in. By default, this setting is set to `false`. The link specified in `RelayState` should be a relative, URL-encoded {kib} URL. For example, the `/app/dashboards#/list` link in `RelayState` parameter would look like this: `RelayState=%2Fapp%2Fdashboards%23%2Flist`. - -|=== +xpack.security.authc.providers.saml..useRelayStateDeepLink {ess-icon}:: +Determines if the provider should treat the `RelayState` parameter as a deep link in {kib} during Identity Provider initiated log in. By default, this setting is set to `false`. The link specified in `RelayState` should be a relative, URL-encoded {kib} URL. For example, the `/app/dashboards#/list` link in `RelayState` parameter would look like this: `RelayState=%2Fapp%2Fdashboards%23%2Flist`. [float] [[oidc-authentication-provider-settings]] -===== OpenID Connect authentication provider settings +==== OpenID Connect authentication provider settings In addition to <>, you can specify the following settings: -[cols="2*<"] -|=== -| `xpack.security.authc.providers.` -`oidc..realm` {ess-icon} -| OpenID Connect realm in {es} that the provider should use. - -|=== +xpack.security.authc.providers.oidc..realm {ess-icon}:: +OpenID Connect realm in {es} that the provider should use. [float] [[anonymous-authentication-provider-settings]] -===== Anonymous authentication provider settings +==== Anonymous authentication provider settings In addition to <>, you can specify the following settings: -[NOTE] -============ -You can configure only one anonymous provider per {kib} instance. -============ - -[cols="2*<"] -|=== -| `xpack.security.authc.providers.` -`anonymous..credentials` {ess-icon} -| Credentials that {kib} should use internally to authenticate anonymous requests to {es}. Possible values are: username and password, API key, or the constant `elasticsearch_anonymous_user` if you want to leverage {ref}/anonymous-access.html[{es} anonymous access]. - -2+a| For example: +NOTE: You can configure only one anonymous provider per {kib} instance. +xpack.security.authc.providers.anonymous..credentials {ess-icon}:: +Credentials that {kib} should use internally to authenticate anonymous requests to {es}. Possible values are: username and password, API key, or the constant `elasticsearch_anonymous_user` if you want to leverage {ref}/anonymous-access.html[{es} anonymous access]. ++ +For example: ++ [source,yaml] ---------------------------------------- # Username and password credentials @@ -187,45 +140,35 @@ xpack.security.authc.providers.anonymous.anonymous1: credentials: "elasticsearch_anonymous_user" ---------------------------------------- -|=== - [float] [[http-authentication-settings]] -===== HTTP authentication settings +==== HTTP authentication settings There is a very limited set of cases when you'd want to change these settings. For more information, refer to <>. -[cols="2*<"] -|=== -| `xpack.security.authc.http.enabled` -| Determines if HTTP authentication should be enabled. By default, this setting is set to `true`. - -| `xpack.security.authc.http.autoSchemesEnabled` -| Determines if HTTP authentication schemes used by the enabled authentication providers should be automatically supported during HTTP authentication. By default, this setting is set to `true`. +xpack.security.authc.http.enabled:: +Determines if HTTP authentication should be enabled. By default, this setting is set to `true`. -| `xpack.security.authc.http.schemes[]` -| List of HTTP authentication schemes that {kib} HTTP authentication should support. By default, this setting is set to `['apikey', 'bearer']` to support HTTP authentication with the <> and <> schemes. +xpack.security.authc.http.autoSchemesEnabled:: +Determines if HTTP authentication schemes used by the enabled authentication providers should be automatically supported during HTTP authentication. By default, this setting is set to `true`. -|=== +xpack.security.authc.http.schemes[]:: +List of HTTP authentication schemes that {kib} HTTP authentication should support. By default, this setting is set to `['apikey', 'bearer']` to support HTTP authentication with the <> and <> schemes. [float] [[login-ui-settings]] -===== Login user interface settings +==== Login user interface settings You can configure the following settings in the `kibana.yml` file. -[cols="2*<"] -|=== -| `xpack.security.loginAssistanceMessage` {ess-icon} -| Adds a message to the login UI. Useful for displaying information about maintenance windows, links to corporate sign up pages, and so on. +xpack.security.loginAssistanceMessage {ess-icon}:: +Adds a message to the login UI. Useful for displaying information about maintenance windows, links to corporate sign up pages, and so on. -| `xpack.security.loginHelp` {ess-icon} -| Adds a message accessible at the login UI with additional help information for the login process. +xpack.security.loginHelp {ess-icon}:: +Adds a message accessible at the login UI with additional help information for the login process. -| `xpack.security.authc.selector.enabled` {ess-icon} -| Determines if the login selector UI should be enabled. By default, this setting is set to `true` if more than one authentication provider is configured. - -|=== +xpack.security.authc.selector.enabled {ess-icon}:: +Determines if the login selector UI should be enabled. By default, this setting is set to `true` if more than one authentication provider is configured. [float] [[security-session-and-cookie-settings]] @@ -233,81 +176,49 @@ You can configure the following settings in the `kibana.yml` file. You can configure the following settings in the `kibana.yml` file. -[cols="2*<"] -|=== -| `xpack.security.cookieName` - | Sets the name of the cookie used for the session. The default value is `"sid"`. - -|[[xpack-security-encryptionKey]] `xpack.security.encryptionKey` - | An arbitrary string of 32 characters or more that is used to encrypt session information. Do **not** expose this key to users of {kib}. By - default, a value is automatically generated in memory. If you use that default - behavior, all sessions are invalidated when {kib} restarts. - In addition, high-availability deployments of {kib} will behave unexpectedly - if this setting isn't the same for all instances of {kib}. - -|[[xpack-security-secureCookies]] `xpack.security.secureCookies` - | Sets the `secure` flag of the session cookie. The default value is `false`. It - is automatically set to `true` if <> is set to `true`. Set - this to `true` if SSL is configured outside of {kib} (for example, you are - routing requests through a load balancer or proxy). - -| [[xpack-security-sameSiteCookies]] `xpack.security.sameSiteCookies` {ess-icon} - | Sets the `SameSite` attribute of the session cookie. This allows you to declare whether your cookie should be restricted to a first-party or same-site context. - Valid values are `Strict`, `Lax`, `None`. - This is *not set* by default, which modern browsers will treat as `Lax`. If you use Kibana embedded in an iframe in modern browsers, you might need to set it to `None`. Setting this value to `None` requires cookies to be sent over a secure connection by setting <>: `true`. - -|[[xpack-session-idleTimeout]] `xpack.security.session.idleTimeout` {ess-icon} - | Ensures that user sessions will expire after a period of inactivity. This and <> are both -highly recommended. You can also specify this setting for <>. If this is set to `0`, then sessions will never expire due to inactivity. By default, this value is 8 hours. - -2+a| -[TIP] -============ -Use a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). -============ - -|[[xpack-session-lifespan]] `xpack.security.session.lifespan` {ess-icon} - | Ensures that user sessions will expire after the defined time period. This behavior is also known as an "absolute timeout". If -this is set to `0`, user sessions could stay active indefinitely. This and <> are both highly -recommended. You can also specify this setting for <>. By default, this value is 30 days. +xpack.security.cookieName:: +Sets the name of the cookie used for the session. The default value is `"sid"`. -2+a| -[TIP] -============ -Use a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). -============ +[[xpack-security-encryptionKey]] xpack.security.encryptionKey:: +An arbitrary string of 32 characters or more that is used to encrypt session information. Do **not** expose this key to users of {kib}. By default, a value is automatically generated in memory. If you use that default behavior, all sessions are invalidated when {kib} restarts. In addition, high-availability deployments of {kib} will behave unexpectedly if this setting isn't the same for all instances of {kib}. -| `xpack.security.session.cleanupInterval` {ess-icon} -| Sets the interval at which {kib} tries to remove expired and invalid sessions from the session index. By default, this value is 1 hour. The minimum value is 10 seconds. +[[xpack-security-secureCookies]] xpack.security.secureCookies:: +Sets the `secure` flag of the session cookie. The default value is `false`. It +is automatically set to `true` if <> is set to `true`. Set this to `true` if SSL is configured outside of {kib} (for example, you are routing requests through a load balancer or proxy). -2+a| -[TIP] -============ -Use a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). -============ +[[xpack-security-sameSiteCookies]] xpack.security.sameSiteCookies {ess-icon}:: +Sets the `SameSite` attribute of the session cookie. This allows you to declare whether your cookie should be restricted to a first-party or same-site context. +Valid values are `Strict`, `Lax`, `None`. +This is *not set* by default, which modern browsers will treat as `Lax`. If you use Kibana embedded in an iframe in modern browsers, you might need to set it to `None`. Setting this value to `None` requires cookies to be sent over a secure connection by setting <>: `true`. -|=== +[[xpack-session-idleTimeout]] xpack.security.session.idleTimeout {ess-icon}:: +Ensures that user sessions will expire after a period of inactivity. This and <> are both highly recommended. You can also specify this setting for <>. If this is set to `0`, then sessions will never expire due to inactivity. By default, this value is 8 hours. ++ +NOTE: Use a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). + +[[xpack-session-lifespan]] xpack.security.session.lifespan {ess-icon}:: +Ensures that user sessions will expire after the defined time period. This behavior is also known as an "absolute timeout". If this is set to `0`, user sessions could stay active indefinitely. This and <> are both highly +recommended. You can also specify this setting for <>. By default, this value is 30 days. ++ +TIP: Use a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). + +xpack.security.session.cleanupInterval {ess-icon}:: +Sets the interval at which {kib} tries to remove expired and invalid sessions from the session index. By default, this value is 1 hour. The minimum value is 10 seconds. ++ +TIP: Use a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). [[security-encrypted-saved-objects-settings]] ==== Encrypted saved objects settings These settings control the encryption of saved objects with sensitive data. For more details, refer to <>. -[IMPORTANT] -============ -In high-availability deployments, make sure you use the same encryption and decryption keys for all instances of {kib}. Although the keys can be specified in clear text in `kibana.yml`, it's recommended to store them securely in the <>. -============ +IMPORTANT: In high-availability deployments, make sure you use the same encryption and decryption keys for all instances of {kib}. Although the keys can be specified in clear text in `kibana.yml`, it's recommended to store them securely in the <>. -[cols="2*<"] -|=== -| [[xpack-encryptedSavedObjects-encryptionKey]] `xpack.encryptedSavedObjects.` -`encryptionKey` -| An arbitrary string of at least 32 characters that is used to encrypt sensitive properties of saved objects before they're stored in {es}. If not set, {kib} will generate a random key on startup, but certain features won't be available until you set the encryption key explicitly. +[[xpack-encryptedSavedObjects-encryptionKey]] xpack.encryptedSavedObjects.encryptionKey:: +An arbitrary string of at least 32 characters that is used to encrypt sensitive properties of saved objects before they're stored in {es}. If not set, {kib} will generate a random key on startup, but certain features won't be available until you set the encryption key explicitly. -| [[xpack-encryptedSavedObjects-keyRotation-decryptionOnlyKeys]] `xpack.encryptedSavedObjects.` -`keyRotation.decryptionOnlyKeys` -| An optional list of previously used encryption keys. Like <>, these must be at least 32 characters in length. {kib} doesn't use these keys for encryption, but may still require them to decrypt some existing saved objects. Use this setting if you wish to change your encryption key, but don't want to lose access to saved objects that were previously encrypted with a different key. -|=== +[[xpack-encryptedSavedObjects-keyRotation-decryptionOnlyKeys]] xpack.encryptedSavedObjects.keyRotation.decryptionOnlyKeys:: +An optional list of previously used encryption keys. Like <>, these must be at least 32 characters in length. {kib} doesn't use these keys for encryption, but may still require them to decrypt some existing saved objects. Use this setting if you wish to change your encryption key, but don't want to lose access to saved objects that were previously encrypted with a different key. [float] [[audit-logging-settings]] @@ -315,18 +226,17 @@ In high-availability deployments, make sure you use the same encryption and decr You can enable audit logging to support compliance, accountability, and security. When enabled, {kib} will capture: -- Who performed an action -- What action was performed -- When the action occurred +* Who performed an action +* What action was performed +* When the action occurred For more details and a reference of audit events, refer to <>. -[cols="2*<"] -|====== -| `xpack.security.audit.enabled` {ess-icon} -| Set to `true` to enable audit logging`. *Default:* `false` - -2+a| For example: +xpack.security.audit.enabled {ess-icon}:: +Set to `true` to enable audit logging`. *Default:* `false` ++ +For example: ++ [source,yaml] ---------------------------------------- xpack.security.audit.enabled: true @@ -346,128 +256,103 @@ xpack.security.audit.appender: <1> <2> Rotates log files every 24 hours. <3> Keeps maximum of 10 log files before deleting older ones. -| `xpack.security.audit.appender` -| Optional. Specifies where audit logs should be written to and how they should be formatted. If no appender is specified, a default appender will be used (see above). - -| `xpack.security.audit.appender.type` -| Required. Specifies where audit logs should be written to. Allowed values are `console`, `file`, or `rolling-file`. +xpack.security.audit.appender:: +Optional. Specifies where audit logs should be written to and how they should be formatted. If no appender is specified, a default appender will be used (see above). +xpack.security.audit.appender.type:: +Required. Specifies where audit logs should be written to. Allowed values are `console`, `file`, or `rolling-file`. ++ Refer to <> and <> for appender specific settings. -| `xpack.security.audit.appender.layout.type` -| Required. Specifies how audit logs should be formatted. Allowed values are `json` or `pattern`. - +xpack.security.audit.appender.layout.type:: +Required. Specifies how audit logs should be formatted. Allowed values are `json` or `pattern`. ++ Refer to <> for layout specific settings. - -2+a| -[TIP] -============ -We recommend using `json` format to allow ingesting {kib} audit logs into {es} using Filebeat. -============ - -|====== ++ +TIP: We recommend using `json` format to allow ingesting {kib} audit logs into {es} using Filebeat. [float] [[audit-logging-file-appender,file appender]] -===== File appender +==== File appender The `file` appender writes to a file and can be configured using the following settings: -[cols="2*<"] -|====== -| `xpack.security.audit.appender.fileName` -| Required. Full file path the log file should be written to. -|====== +xpack.security.audit.appender.fileName:: +Required. Full file path the log file should be written to. [float] [[audit-logging-rolling-file-appender, rolling file appender]] -===== Rolling file appender +==== Rolling file appender The `rolling-file` appender writes to a file and rotates it using a rolling strategy, when a particular policy is triggered: -[cols="2*<"] -|====== -| `xpack.security.audit.appender.fileName` -| Required. Full file path the log file should be written to. - -| `xpack.security.audit.appender.policy.type` -| Specifies when a rollover should occur. Allowed values are `size-limit` and `time-interval`. *Default:* `time-interval`. +xpack.security.audit.appender.fileName:: +Required. Full file path the log file should be written to. +xpack.security.audit.appender.policy.type:: +Specifies when a rollover should occur. Allowed values are `size-limit` and `time-interval`. *Default:* `time-interval`. ++ Refer to <> and <> for policy specific settings. -| `xpack.security.audit.appender.strategy.type` -| Specifies how the rollover should occur. Only allowed value is currently `numeric`. *Default:* `numeric` +xpack.security.audit.appender.strategy.type:: +Specifies how the rollover should occur. Only allowed value is currently `numeric`. *Default:* `numeric` ++ Refer to <> for strategy specific settings. -|====== [float] [[audit-logging-size-limit-policy, size limit policy]] -===== Size limit triggering policy +==== Size limit triggering policy The `size-limit` triggering policy will rotate the file when it reaches a certain size: -[cols="2*<"] -|====== -| `xpack.security.audit.appender.policy.size` -| Maximum size the log file should reach before a rollover should be performed. *Default:* `100mb` -|====== +xpack.security.audit.appender.policy.size:: +Maximum size the log file should reach before a rollover should be performed. *Default:* `100mb` [float] [[audit-logging-time-interval-policy, time interval policy]] -===== Time interval triggering policy +==== Time interval triggering policy The `time-interval` triggering policy will rotate the file every given interval of time: -[cols="2*<"] -|====== -| `xpack.security.audit.appender.policy.interval` -| How often a rollover should occur. *Default:* `24h` +xpack.security.audit.appender.policy.interval:: +How often a rollover should occur. *Default:* `24h` -| `xpack.security.audit.appender.policy.modulate` -| Whether the interval should be adjusted to cause the next rollover to occur on the interval boundary. *Default:* `true` -|====== +xpack.security.audit.appender.policy.modulate:: +Whether the interval should be adjusted to cause the next rollover to occur on the interval boundary. *Default:* `true` [float] [[audit-logging-numeric-strategy, numeric strategy]] -===== Numeric rolling strategy +==== Numeric rolling strategy The `numeric` rolling strategy will suffix the log file with a given pattern when rolling over, and will retain a fixed number of rolled files: -[cols="2*<"] -|====== -| `xpack.security.audit.appender.strategy.pattern` -| Suffix to append to the file name when rolling over. Must include `%i`. *Default:* `-%i` +xpack.security.audit.appender.strategy.pattern:: +Suffix to append to the file name when rolling over. Must include `%i`. *Default:* `-%i` -| `xpack.security.audit.appender.strategy.max` -| Maximum number of files to keep. Once this number is reached, oldest files will be deleted. *Default:* `7` -|====== +xpack.security.audit.appender.strategy.max:: +Maximum number of files to keep. Once this number is reached, oldest files will be deleted. *Default:* `7` [float] [[audit-logging-pattern-layout, pattern layout]] -===== Pattern layout +==== Pattern layout The `pattern` layout outputs a string, formatted using a pattern with special placeholders, which will be replaced with data from the actual log message: -[cols="2*<"] -|====== -| `xpack.security.audit.appender.layout.pattern` -| Optional. Specifies how the log line should be formatted. *Default:* `[%date][%level][%logger]%meta %message` +xpack.security.audit.appender.layout.pattern:: +Optional. Specifies how the log line should be formatted. *Default:* `[%date][%level][%logger]%meta %message` -| `xpack.security.audit.appender.layout.highlight` -| Optional. Set to `true` to enable highlighting log messages with colors. -|====== +xpack.security.audit.appender.layout.highlight:: +Optional. Set to `true` to enable highlighting log messages with colors. [float] [[audit-logging-ignore-filters]] -===== Ignore filters - -[cols="2*<"] -|====== -| `xpack.security.audit.ignore_filters[]` {ess-icon} -| List of filters that determine which events should be excluded from the audit log. An event will get filtered out if at least one of the provided filters matches. - -2+a| For example: +==== Ignore filters +xpack.security.audit.ignore_filters[] {ess-icon}:: +List of filters that determine which events should be excluded from the audit log. An event will get filtered out if at least one of the provided filters matches. ++ +For example: ++ [source,yaml] ---------------------------------------- xpack.security.audit.ignore_filters: @@ -478,15 +363,14 @@ xpack.security.audit.ignore_filters: <1> Filters out HTTP request events <2> Filters out any data write events -| `xpack.security.audit.ignore_filters[].actions[]` {ess-icon} -| List of values matched against the `event.action` field of an audit event. Refer to <> for a list of available events. +xpack.security.audit.ignore_filters[].actions[] {ess-icon}:: +List of values matched against the `event.action` field of an audit event. Refer to <> for a list of available events. -| `xpack.security.audit.ignore_filters[].categories[]` {ess-icon} -| List of values matched against the `event.category` field of an audit event. Refer to https://www.elastic.co/guide/en/ecs/1.5/ecs-allowed-values-event-category.html[ECS categorization field] for allowed values. +xpack.security.audit.ignore_filters[].categories[] {ess-icon}:: +List of values matched against the `event.category` field of an audit event. Refer to https://www.elastic.co/guide/en/ecs/1.5/ecs-allowed-values-event-category.html[ECS categorization field] for allowed values. -| `xpack.security.audit.ignore_filters[].types[]` {ess-icon} -| List of values matched against the `event.type` field of an audit event. Refer to https://www.elastic.co/guide/en/ecs/1.5/ecs-allowed-values-event-type.html[ECS type field] for allowed values. +xpack.security.audit.ignore_filters[].types[] {ess-icon}:: +List of values matched against the `event.type` field of an audit event. Refer to https://www.elastic.co/guide/en/ecs/1.5/ecs-allowed-values-event-type.html[ECS type field] for allowed values. -| `xpack.security.audit.ignore_filters[].outcomes[]` {ess-icon} -| List of values matched against the `event.outcome` field of an audit event. Refer to https://www.elastic.co/guide/en/ecs/1.5/ecs-allowed-values-event-outcome.html[ECS outcome field] for allowed values. -|====== +xpack.security.audit.ignore_filters[].outcomes[] {ess-icon}:: +List of values matched against the `event.outcome` field of an audit event. Refer to https://www.elastic.co/guide/en/ecs/1.5/ecs-allowed-values-event-outcome.html[ECS outcome field] for allowed values. \ No newline at end of file diff --git a/docs/setup/install.asciidoc b/docs/setup/install.asciidoc index 8b64bdf5fe2a2..ac49946d877bc 100644 --- a/docs/setup/install.asciidoc +++ b/docs/setup/install.asciidoc @@ -46,12 +46,6 @@ downloaded from the Elastic Docker Registry. + <> -`brew`:: - -Formulae are available from the Elastic Homebrew tap for installing {kib} on macOS with the Homebrew package manager. -+ -<> - IMPORTANT: If your Elasticsearch installation is protected by {ref}/elasticsearch-security.html[{stack-security-features}] see {kibana-ref}/using-kibana-with-security.html[Configuring security in {kib}] for @@ -66,5 +60,3 @@ include::install/deb.asciidoc[] include::install/rpm.asciidoc[] include::{kib-repo-dir}/setup/docker.asciidoc[] - -include::install/brew.asciidoc[] diff --git a/docs/setup/install/brew-running.asciidoc b/docs/setup/install/brew-running.asciidoc deleted file mode 100644 index d73102b098ec1..0000000000000 --- a/docs/setup/install/brew-running.asciidoc +++ /dev/null @@ -1,9 +0,0 @@ -==== Run {kib} with `brew services` - -With Homebrew, Kibana can be started and stopped as follows: - -[source,sh] --------------------------------------------------- -brew services start elastic/tap/kibana-full -brew services stop elastic/tap/kibana-full --------------------------------------------------- diff --git a/docs/setup/install/brew.asciidoc b/docs/setup/install/brew.asciidoc deleted file mode 100644 index eeba869a259d4..0000000000000 --- a/docs/setup/install/brew.asciidoc +++ /dev/null @@ -1,65 +0,0 @@ -[[brew]] -=== Install {kib} on macOS with Homebrew -++++ -Install on macOS with Homebrew -++++ - -Elastic publishes Homebrew formulae so you can install {kib} with the https://brew.sh/[Homebrew] package manager. - -To install with Homebrew, you first need to tap the Elastic Homebrew repository: - -[source,sh] -------------------------- -brew tap elastic/tap -------------------------- - -Once you've tapped the Elastic Homebrew repo, you can use `brew install` to -install the **latest version** of {kib}: - -[source,sh] -------------------------- -brew install elastic/tap/kibana-full -------------------------- - -[[brew-layout]] -==== Directory layout for Homebrew installs - -When you install {kib} with `brew install`, the config files, logs, -and data directory are stored in the following locations. - -[cols="> -when required. +When required, {kib} automatically migrates <>. In case of an upgrade failure, you can roll back to an earlier version of {kib}. To roll back, you **must** have a {ref}/snapshot-restore.html[backup snapshot] that includes the `kibana` feature -state. Snapshots include this feature state by default. +state. By default, snapshots include the `kibana` feature state. ==== -For more information about upgrading, +For more information about upgrading, refer to {stack-ref}/upgrading-elastic-stack.html[Upgrading to Elastic {version}.] -IMPORTANT: You can upgrade to pre-release versions for testing, -but upgrading from a pre-release to the General Available version is not supported. -Pre-releases should only be used for testing in a temporary environment. +IMPORTANT: You can upgrade to pre-release versions for testing, +but upgrading from a pre-release to the General Available version is unsupported. +You should use pre-release versions only for testing in a temporary environment. + +[float] +=== Upgrading multiple {kib} instances +When upgrading several {kib} instances connected to the same {es} cluster, +ensure that all outdated instances are shut down before starting the upgrade. + +Rolling upgrades are unsupported in {kib}. However, when outdated instances are shut down, you can start all upgraded instances in parallel, +which allows all instances to participate in the upgrade migration in parallel. + +For large deployments with more than 10 {kib} instances, and more than 10,000 saved objects, +you can reduce the upgrade downtime by bringing up a single {kib} instance and waiting for it to +complete the upgrade migration before bringing up the remaining instances. + +[float] +[[preventing-migration-failures]] +=== Preparing for migration + +There are extra steps you can follow to ensure you are ready for migration. + +[float] +==== Ensure your {es} cluster is healthy +Problems with your {es} cluster can prevent {kib} upgrades from succeeding. Ensure that your cluster has: + + * Enough free disk space, at least twice the amount of storage taken up by the `.kibana` and `.kibana_task_manager` indices + * Sufficient heap size + * A "green" cluster status + +[float] +==== Ensure that all {kib} instances are the same +When you perform an upgrade migration of different {kib} versions, the migration can fail. +Ensure that all {kib} instances are running the same version, configuration, and plugins. + +[float] +==== Back up your data +Be sure to have a {ref}/snapshot-restore.html[snapshot] of all your data before attempting a migration. +If something goes wrong during migration, you can restore from the snapshot and try again. + +Review the <> and how to prevent them. + + +include::upgrade/saved-objects-migration.asciidoc[] -include::upgrade/upgrade-migrations.asciidoc[leveloffset=-1] +include::upgrade/resolving-migration-failures.asciidoc[] -include::upgrade/logging-configuration-changes.asciidoc[] +include::upgrade/rollback-migration.asciidoc[] diff --git a/docs/setup/upgrade/resolving-migration-failures.asciidoc b/docs/setup/upgrade/resolving-migration-failures.asciidoc new file mode 100644 index 0000000000000..454dfe948fe4e --- /dev/null +++ b/docs/setup/upgrade/resolving-migration-failures.asciidoc @@ -0,0 +1,125 @@ +[[resolve-migrations-failures]] +=== Resolve migration failures + +Migrating {kib} primarily involves migrating saved object documents to be compatible +with the new version. + +[float] +==== Resolve saved object migration failures + +If {kib} unexpectedly terminates while migrating a saved object index, {kib} automatically attempts to +perform the migration again when the process restarts. Do not delete any saved objects indices to +to fix a failed migration. Unlike previous versions, {kib} 7.12.0 and +later does not require deleting indices to release a failed migration lock. + +If upgrade migrations fail repeatedly, refer to +<>. +When you address the root cause for the migration failure, +{kib} automatically retries the migration. +If you're unable to resolve a failed migration, contact Support. + + +[float] +[[upgrade-migrations-old-indices]] +==== Handle old `.kibana_N` indices + +After the migrations complete, multiple {kib} indices are created in {es}: (`.kibana_1`, `.kibana_2`, `.kibana_7.12.0` etc). +{kib} only uses the index that the `.kibana` and `.kibana_task_manager` aliases point to. +The other {kib} indices can be safely deleted, but are left around as a matter of historical record, and to facilitate rolling {kib} back to a previous version. + +[float] +==== Handle known issues with {fleet} beta +If you see a`timeout_exception` or `receive_timeout_transport_exception` error, +it might be from a known known issue in 7.12.0 if you tried the {fleet} beta. +Upgrade migrations fail because of a large number of documents in the `.kibana` index, +which causes {kib} to log errors such as: + +[source,sh] +-------------------------------------------- +Error: Unable to complete saved object migrations for the [.kibana] index. Please check the health of your Elasticsearch cluster and try again. Error: [receive_timeout_transport_exception]: [instance-0000000002][10.32.1.112:19541][cluster:monitor/task/get] request_id [2648] timed out after [59940ms] + +Error: Unable to complete saved object migrations for the [.kibana] index. Please check the health of your Elasticsearch cluster and try again. Error: [timeout_exception]: Timed out waiting for completion of [org.elasticsearch.index.reindex.BulkByScrollTask@6a74c54] +-------------------------------------------- + +For instructions on how to mitigate the known issue, refer to https://github.com/elastic/kibana/issues/95321[the GitHub issue]. + + +[float] +==== Handle corrupt saved objects +To find and remedy problems caused by corrupt documents, we highly recommend testing your {kib} upgrade in a development cluster, +especially when there are custom integrations that create saved objects in your environment. + +Saved objects that are corrupted through manual editing or integrations cause migration +failures with a log message, such as `Unable to migrate the corrupt Saved Object document ...`. +For a successful upgrade migration, you must fix or delete corrupt documents. + +For example, you receive the following error message: + +[source,sh] +-------------------------------------------- +Unable to migrate the corrupt saved object document with _id: 'marketing_space:dashboard:e3c5fc71-ac71-4805-bcab-2bcc9cc93275'. To allow migrations to proceed, please delete this document from the [.kibana_7.12.0_001] index. +-------------------------------------------- + +To delete the documents that cause migrations to fail, take the following steps: + +. Remove the write block which the migration system has placed on the previous index: ++ +[source,sh] +-------------------------------------------- +PUT .kibana_7.12.1_001/_settings +{ + "index": { + "blocks.write": false + } +} +-------------------------------------------- + +. Delete the corrupt document: ++ +[source,sh] +-------------------------------------------- +DELETE .kibana_7.12.0_001/_doc/marketing_space:dashboard:e3c5fc71-ac71-4805-bcab-2bcc9cc93275 +-------------------------------------------- + +. Restart {kib}. ++ +The dashboard with the `e3c5fc71-ac71-4805-bcab-2bcc9cc93275` ID that belongs to the `marketing_space` space **is no longer available**. + +[float] +[[unknown-saved-object-types]] +==== Handle documents for unknown saved objects +Migrations will fail if saved objects belong to an unknown +saved object type. Unknown saved objects are typically caused by +to the {es} index, or by disabling a plugin that had previously +created a saved object. + +We recommend using the {kibana-ref-all}/7.17/upgrade-assistant.html[Upgrade Assistant] +to discover and remedy any unknown saved object types. {kib} version 7.17.0 deployments containing unknown saved +object types will also log the following warning message: + +[source,sh] +-------------------------------------------- +CHECK_UNKNOWN_DOCUMENTS Upgrades will fail for 8.0+ because documents were found for unknown saved object types. To ensure that upgrades will succeed in the future, either re-enable plugins or delete these documents from the ".kibana_7.17.0_001" index after the current upgrade completes. +-------------------------------------------- + +If you fail to remedy this, your upgrade to 8.0+ will fail with a message like: + +[source,sh] +-------------------------------------------- +Unable to complete saved object migrations for the [.kibana] index: Migration failed because documents were found for unknown saved object types. To proceed with the migration, please delete these documents from the ".kibana_7.17.0_001" index. +-------------------------------------------- + +[float] +==== Handle incompatible settings or mappings +Matching index templates that specify `settings.refresh_interval` or +`mappings` are known to interfere with {kib} upgrades. +This can happen when index templates are defined manually. + +To make sure the index templates won't apply to new `.kibana*` indices, narrow down the {data-sources} of any user-defined index templates. + +[float] +==== Handle incompatible `xpack.tasks.index` configuration setting +In {kib} 7.5.0 and earlier, when the task manager index is set to `.tasks` +with the configuration setting `xpack.tasks.index: ".tasks"`, +upgrade migrations fail. In {kib} 7.5.1 and later, the incompatible configuration +setting prevents upgrade migrations from starting. diff --git a/docs/setup/upgrade/rollback-migration.asciidoc b/docs/setup/upgrade/rollback-migration.asciidoc new file mode 100644 index 0000000000000..1b87d0f335b8c --- /dev/null +++ b/docs/setup/upgrade/rollback-migration.asciidoc @@ -0,0 +1,41 @@ +[[upgrade-migrations-rolling-back]] +=== Roll back to a previous version of {kib} + +If you've followed <> +and <>, and +{kib} is still unable to successfully upgrade, rollback {kib} until +you're able to identify and fix the root cause. + +WARNING: Before you roll back {kib}, ensure that the version you want to roll back to is compatible with +your {es} cluster. If the version you want to roll back to is not compatible, you must also rollback {es}. +Any changes made after an upgrade are lost when you roll back to a previous version. + +To roll back after a failed upgrade migration, you must also rollback the saved object indices to be compatible with the previous {kib} version. + +[float] +==== Roll back by restoring a backup snapshot + +. Before proceeding, {ref}/snapshots-take-snapshot.html[take a snapshot] that contains the `kibana` feature state. + By default, snapshots include the `kibana` feature state. +. To make sure no {kib} instances are performing an upgrade migration, shut down all {kib} instances. +. To delete all saved object indices, use `DELETE /.kibana*`. +. {ref}/snapshots-restore-snapshot.html[Restore] the `kibana` feature state from the snapshot. +. Start all {kib} instances on the older version you want to rollback to. + +[float] +==== (Not recommended) Roll back without a backup snapshot + +. To make sure no {kib} instances are performing an upgrade migration, shut down all {kib} instances. +. {ref}/snapshots-take-snapshot.html[Take a snapshot] that includes the `kibana` feature state. By default, snapshots include the `kibana` feature state. +. Delete the version-specific indices created by the failed upgrade migration. ++ +For example, to rollback from a failed upgrade +to v7.12.0, use `DELETE /.kibana_7.12.0_*,.kibana_task_manager_7.12.0_*`. +. Inspect the output of `GET /_cat/aliases`. ++ +If the `.kibana` or `.kibana_task_manager` aliases are missing, you must create them manually. +Find the latest index from the output of `GET /_cat/indices` and create the missing alias to point to the latest index. +For example, if the `.kibana` alias is missing, and the latest index is `.kibana_3`, create a new alias using `POST /.kibana_3/_aliases/.kibana`. +. To remove the write block from the roll back indices, use +`PUT /.kibana,.kibana_task_manager/_settings {"index.blocks.write": false}` +. Start {kib} on the older version you want to rollback to. diff --git a/docs/setup/upgrade/saved-objects-migration.asciidoc b/docs/setup/upgrade/saved-objects-migration.asciidoc new file mode 100644 index 0000000000000..cc4406f8cdd1f --- /dev/null +++ b/docs/setup/upgrade/saved-objects-migration.asciidoc @@ -0,0 +1,39 @@ +[[saved-object-migrations]] +=== Migrate saved objects + +Each time you upgrade {kib}, an upgrade migration is performed to ensure that all <> are compatible with the new version. + +NOTE: To help you prepare for the upgrade to 8.0.0, 7.17.0 includes an https://www.elastic.co/guide/en/kibana/7.17/upgrade-assistant.html[*Upgrade Assistant*]. +To access the assistant, go to *Stack Management > Upgrade Assistant*. + +WARNING: {kib} 7.12.0 and later uses a new migration process and index naming scheme. Before you upgrade, read the documentation for your version of {kib}. + +WARNING: The following instructions assumes {kib} is using the default index names. If the `kibana.index` or `xpack.tasks.index` configuration settings are different from the default, adapt the instructions accordingly. + +[float] +[[upgrade-migrations-process]] +==== How saved objects migrations work + +Saved objects are stored in two indices: + +* `.kibana_{kibana_version}_001`, e.g. for {kib} 7.12.0 `.kibana_7.12.0_001`. +* `.kibana_task_manager_{kibana_version}_001`, e.g. for {kib} 7.12.0 `.kibana_task_manager_7.12.0_001`. + +The index aliases `.kibana` and `.kibana_task_manager` always point to +the most up-to-date saved object indices. + +When you start a new {kib} installation, an upgrade migration is performed before starting plugins or serving HTTP traffic. +Before you upgrade, shut down old nodes to prevent losing acknowledged writes. +To reduce the likelihood of old nodes losing acknowledged writes, {kib} 7.12.0 and later +adds a write block to the outdated index. Table 1 lists the saved objects indices used by previous {kib} versions. + +.Saved object indices and aliases per {kib} version +[options="header"] +|======================= +|Upgrading from version | Outdated index (alias) +| 6.5.0 through 7.3.x | `.kibana_N` (`.kibana` alias) +| 7.4.0 through 7.11.x +| `.kibana_N` (`.kibana` alias) + +`.kibana_task_manager_N` (`.kibana_task_manager` alias) +|======================= diff --git a/docs/setup/upgrade/upgrade-migrations.asciidoc b/docs/setup/upgrade/upgrade-migrations.asciidoc deleted file mode 100644 index fc921f9118bdf..0000000000000 --- a/docs/setup/upgrade/upgrade-migrations.asciidoc +++ /dev/null @@ -1,176 +0,0 @@ -[float] -[[saved-object-migrations]] -=== Saved object migrations - -Every time {kib} is upgraded it will perform an upgrade migration to ensure that all <> are compatible with the new version. - -NOTE: 6.7 includes an https://www.elastic.co/guide/en/kibana/6.7/upgrade-assistant.html[Upgrade Assistant] -to help you prepare for your upgrade to 7.0. To access the assistant, go to *Management > 7.0 Upgrade Assistant*. - -WARNING: {kib} 7.12.0 and later uses a new migration process and index naming scheme. Be sure to read the documentation for your version of {kib} before proceeding. - -WARNING: The following instructions assumes {kib} is using the default index names. If the `kibana.index` or `xpack.tasks.index` configuration settings were changed these instructions will have to be adapted accordingly. - -[float] -[[upgrade-migrations-process]] -==== Background - -Saved objects are stored in two indices: - -* `.kibana_{kibana_version}_001`, e.g. for Kibana v7.12.0 `.kibana_7.12.0_001`. -* `.kibana_task_manager_{kibana_version}_001`, e.g. for Kibana v7.12.0 `.kibana_task_manager_7.12.0_001`. - -The index aliases `.kibana` and `.kibana_task_manager` will always point to -the most up-to-date saved object indices. - -The first time a newer {kib} starts, it will first perform an upgrade migration before starting plugins or serving HTTP traffic. To prevent losing acknowledged writes old nodes should be shutdown before starting the upgrade. To reduce the likelihood of old nodes losing acknowledged writes, {kib} 7.12.0 and later will add a write block to the outdated index. Table 1 lists the saved objects indices used by previous versions of {kib}. - -.Saved object indices and aliases per {kib} version -[options="header"] -|======================= -|Upgrading from version | Outdated index (alias) -| 6.0.0 through 6.4.x | `.kibana` - -`.kibana_task_manager_7.12.0_001` (`.kibana_task_manager` alias) -| 6.5.0 through 7.3.x | `.kibana_N` (`.kibana` alias) -| 7.4.0 through 7.11.x -| `.kibana_N` (`.kibana` alias) - -`.kibana_task_manager_N` (`.kibana_task_manager` alias) -|======================= - -==== Upgrading multiple {kib} instances -When upgrading several {kib} instances connected to the same {es} cluster, ensure that all outdated instances are shutdown before starting the upgrade. - -Kibana does not support rolling upgrades. However, once outdated instances are shutdown, all upgraded instances can be started in parallel in which case all instances will participate in the upgrade migration in parallel. - -For large deployments with more than 10 {kib} instances and more than 10 000 saved objects, the upgrade downtime can be reduced by bringing up a single {kib} instance and waiting for it to complete the upgrade migration before bringing up the remaining instances. - -[float] -[[preventing-migration-failures]] -==== Preventing migration failures -This section highlights common causes of {kib} upgrade failures and how to prevent them. - -[float] -===== timeout_exception or receive_timeout_transport_exception -There is a known issue in v7.12.0 for users who tried the fleet beta. Upgrade migrations fail because of a large number of documents in the `.kibana` index. - -This can cause Kibana to log errors like: - -[source,sh] --------------------------------------------- -Error: Unable to complete saved object migrations for the [.kibana] index. Please check the health of your Elasticsearch cluster and try again. Error: [receive_timeout_transport_exception]: [instance-0000000002][10.32.1.112:19541][cluster:monitor/task/get] request_id [2648] timed out after [59940ms] - -Error: Unable to complete saved object migrations for the [.kibana] index. Please check the health of your Elasticsearch cluster and try again. Error: [timeout_exception]: Timed out waiting for completion of [org.elasticsearch.index.reindex.BulkByScrollTask@6a74c54] --------------------------------------------- - -See https://github.com/elastic/kibana/issues/95321 for instructions to work around this issue. - -[float] -===== Corrupt saved objects -We highly recommend testing your {kib} upgrade in a development cluster to discover and remedy problems caused by corrupt documents, especially when there are custom integrations creating saved objects in your environment. - -Saved objects that were corrupted through manual editing or integrations will cause migration failures with a log message like `Failed to transform document. Transform: index-pattern:7.0.0\n Doc: {...}` or `Unable to migrate the corrupt Saved Object document ...`. Corrupt documents will have to be fixed or deleted before an upgrade migration can succeed. - -For example, given the following error message: - -[source,sh] --------------------------------------------- -Unable to migrate the corrupt saved object document with _id: 'marketing_space:dashboard:e3c5fc71-ac71-4805-bcab-2bcc9cc93275'. To allow migrations to proceed, please delete this document from the [.kibana_7.12.0_001] index. --------------------------------------------- - -The following steps must be followed to delete the document that is causing the migration to fail: - -. Remove the write block which the migration system has placed on the previous index: -+ -[source,sh] --------------------------------------------- -PUT .kibana_7.12.1_001/_settings -{ - "index": { - "blocks.write": false - } -} --------------------------------------------- - -. Delete the corrupt document: -+ -[source,sh] --------------------------------------------- -DELETE .kibana_7.12.0_001/_doc/marketing_space:dashboard:e3c5fc71-ac71-4805-bcab-2bcc9cc93275 --------------------------------------------- - -. Restart {kib}. - -In this example, the Dashboard with ID `e3c5fc71-ac71-4805-bcab-2bcc9cc93275` that belongs to the space `marketing_space` **will no longer be available**. - -Be sure you have a snapshot before you delete the corrupt document. If restoring from a snapshot is not an option, it is recommended to also delete the `temp` and `target` indices the migration created before restarting {kib} and retrying. - -[float] -===== User defined index templates that causes new `.kibana*` indices to have incompatible settings or mappings -Matching index templates which specify `settings.refresh_interval` or `mappings` are known to interfere with {kib} upgrades. - -Prevention: narrow down the index patterns of any user-defined index templates to ensure that these won't apply to new `.kibana*` indices. - -Note: {kib} < 6.5 creates it's own index template called `kibana_index_template:.kibana` and index pattern `.kibana`. This index template will not interfere and does not need to be changed or removed. - -[float] -===== An unhealthy {es} cluster -Problems with your {es} cluster can prevent {kib} upgrades from succeeding. Ensure that your cluster has: - - * enough free disk space, at least twice the amount of storage taken up by the `.kibana` and `.kibana_task_manager` indices - * sufficient heap size - * a "green" cluster status - -[float] -===== Different versions of {kib} connected to the same {es} index -When different versions of {kib} are attempting an upgrade migration in parallel this can lead to migration failures. Ensure that all {kib} instances are running the same version, configuration and plugins. - -[float] -===== Incompatible `xpack.tasks.index` configuration setting -For {kib} versions prior to 7.5.1, if the task manager index is set to `.tasks` with the configuration setting `xpack.tasks.index: ".tasks"`, upgrade migrations will fail. {kib} 7.5.1 and later prevents this by refusing to start with an incompatible configuration setting. - -[float] -[[resolve-migrations-failures]] -==== Resolving migration failures - -If {kib} terminates unexpectedly while migrating a saved object index it will automatically attempt to perform the migration again once the process has restarted. Do not delete any saved objects indices to attempt to fix a failed migration. Unlike previous versions, {kib} version 7.12.0 and later does not require deleting any indices to release a failed migration lock. - -If upgrade migrations fail repeatedly, follow the advice in (preventing migration failures)[preventing-migration-failures]. Once the root cause for the migration failure has been addressed, {kib} will automatically retry the migration without any further intervention. If you're unable to resolve a failed migration following these steps, please contact support. - -[float] -[[upgrade-migrations-rolling-back]] -==== Rolling back to a previous version of {kib} - -If you've followed the advice in (preventing migration failures)[preventing-migration-failures] and (resolving migration failures)[resolve-migrations-failures] and {kib} is still not able to upgrade successfully, you might choose to rollback {kib} until you're able to identify and fix the root cause. - -WARNING: Before rolling back {kib}, ensure that the version you wish to rollback to is compatible with your {es} cluster. If the version you're rolling back to is not compatible, you will have to also rollback {es}. + -Any changes made after an upgrade will be lost when rolling back to a previous version. - -In order to rollback after a failed upgrade migration, the saved object indices have to be rolled back to be compatible with the previous {kibana} version. - -[float] -===== Rollback by restoring a backup snapshot: - -1. Before proceeding, {ref}/snapshots-take-snapshot.html[take a snapshot] that contains the `kibana` feature state. - Snapshots include this feature state by default. -2. Shutdown all {kib} instances to be 100% sure that there are no instances currently performing a migration. -3. Delete all saved object indices with `DELETE /.kibana*` -4. {ref}/snapshots-restore-snapshot.html[Restore] the `kibana` feature state from the snapshot. -5. Start up all {kib} instances on the older version you wish to rollback to. - -[float] -===== (Not recommended) Rollback without a backup snapshot: - -1. Shutdown all {kib} instances to be 100% sure that there are no {kib} instances currently performing a migration. -2. {ref}/snapshots-take-snapshot.html[Take a snapshot] that includes the `kibana` feature state. Snapshots include this feature state by default. -3. Delete the version specific indices created by the failed upgrade migration. E.g. if you wish to rollback from a failed upgrade to v7.12.0 `DELETE /.kibana_7.12.0_*,.kibana_task_manager_7.12.0_*` -4. Inspect the output of `GET /_cat/aliases`. If either the `.kibana` and/or `.kibana_task_manager` alias is missing, these will have to be created manually. Find the latest index from the output of `GET /_cat/indices` and create the missing alias to point to the latest index. E.g. if the `.kibana` alias was missing and the latest index is `.kibana_3` create a new alias with `POST /.kibana_3/_aliases/.kibana`. -5. Remove the write block from the rollback indices. `PUT /.kibana,.kibana_task_manager/_settings {"index.blocks.write": false}` -6. Start up {kib} on the older version you wish to rollback to. - -[float] -[[upgrade-migrations-old-indices]] -==== Handling old `.kibana_N` indices - -After migrations have completed, there will be multiple {kib} indices in {es}: (`.kibana_1`, `.kibana_2`, `.kibana_7.12.0` etc). {kib} only uses the index that the `.kibana` and `.kibana_task_manager` alias points to. The other {kib} indices can be safely deleted, but are left around as a matter of historical record, and to facilitate rolling {kib} back to a previous version. diff --git a/docs/setup/upgrade/upgrade-standard.asciidoc b/docs/setup/upgrade/upgrade-standard.asciidoc index b43da6aef9765..6854ead7531a8 100644 --- a/docs/setup/upgrade/upgrade-standard.asciidoc +++ b/docs/setup/upgrade/upgrade-standard.asciidoc @@ -1,8 +1,8 @@ [[upgrade-standard]] === Standard upgrade -NOTE: 6.7 includes an https://www.elastic.co/guide/en/kibana/6.7/upgrade-assistant.html[Upgrade Assistant] -to help you prepare for your upgrade to 7.0. To access the assistant, go to *Management > 7.0 Upgrade Assistant*. +NOTE: 7.17 includes an https://www.elastic.co/guide/en/kibana/7.17/upgrade-assistant.html[Upgrade Assistant] +to help you prepare for your upgrade to 8.0. To access the assistant, go to *Stack Management > Upgrade Assistant*. [IMPORTANT] =========================================== @@ -29,15 +29,9 @@ Different versions of {kib} running against the same {es} index, such as during . Use `rpm` or `dpkg` to install the new package. All files should be placed in their proper locations and config files should not be overwritten. + -[NOTE] --- -{kib} 4.x used a different config location than 5.0+, so if you're upgrading -from 4.x, you will need to copy the configurations from your old config -(`/opt/kibana/config/kibana.yml`) to your new config -(`/etc/kibana/kibana.yml`). Make sure you remove or update any configurations -that are indicated in the <> documentation +that are indicated in the <> documentation otherwise {kib} will fail to start. -- . Upgrade any plugins by removing the existing plugin and reinstalling the @@ -58,7 +52,7 @@ and becomes a new instance in the monitoring data. -- . Copy the files from the `config` directory from your old installation to your new installation. Make sure you remove or update any configurations that are - indicated in the <> documentation + indicated in the <> documentation otherwise {kib} will fail to start. . Copy the files from the `data` directory from your old installation to your new installation. diff --git a/docs/user/dashboard/tsvb.asciidoc b/docs/user/dashboard/tsvb.asciidoc index a1bad870dde46..a097e34b20911 100644 --- a/docs/user/dashboard/tsvb.asciidoc +++ b/docs/user/dashboard/tsvb.asciidoc @@ -191,6 +191,30 @@ For example `dashboards#/view/f193ca90-c9f4-11eb-b038-dd3270053a27`. . In the toolbar, click *Save as*, then make sure *Store time with dashboard* is deselected. ==== +[discrete] +[[how-do-i-base-drilldowns-on-data]] +.*How do I base drilldown URLs on my data?* +[%collapsible] +==== + +You can build drilldown URLs dynamically with your visualization data. + +Do this by adding the `{{key}}` placeholder to your URL + +For example `https://example.org/{{key}}` + +This instructs TSVB to substitute the value from your visualization wherever it sees `{{key}}`. + +If your data contain reserved or invalid URL characters such as "#" or "&", you should apply a transform to URL-encode the key like this `{{encodeURIComponent key}}`. If you are dynamically constructing a drilldown to another location in Kibana (for example, clicking a table row takes to you a value-scoped saved search), you will likely want to Rison-encode your key as it may contain invalid Rison characters. (https://github.com/Nanonid/rison#rison---compact-data-in-uris[Rison] is the serialization format many parts of Kibana use to store information in their URL.) + +For example: `discover#/view/0ac50180-82d9-11ec-9f4a-55de56b00cc0?_a=(filters:!((query:(match_phrase:(foo.keyword:{{rison key}})))))` + +If both conditions apply, you can cover all cases by applying both transforms: `{{encodeURIComponent (rison key)}}`. + +Technical note: TSVB uses https://handlebarsjs.com/[Handlebars] to perform these interpolations. `rison` and `encodeURIComponent` are custom Handlebars helpers provided by Kibana. + +==== + [discrete] [[why-is-my-tsvb-visualiztion-missing-data]] .*Why is my TSVB visualization missing data?* diff --git a/docs/user/dashboard/vega-reference.asciidoc b/docs/user/dashboard/vega-reference.asciidoc index 860261129a364..b9fdf0c9a7ec5 100644 --- a/docs/user/dashboard/vega-reference.asciidoc +++ b/docs/user/dashboard/vega-reference.asciidoc @@ -429,8 +429,9 @@ To keep signal values set `restoreSignalValuesOnRefresh: true` in the Vega confi /** * @param {object} query Elastic Query DSL snippet, as used in the query DSL editor * @param {string} [index] as defined in Kibana, or default if missing + * @param {string} Custom label of the filter shown in the filter bar */ -kibanaAddFilter(query, index) +kibanaAddFilter(query, index, alias) /** * @param {object} query Elastic Query DSL snippet, as used in the query DSL editor diff --git a/docs/user/management.asciidoc b/docs/user/management.asciidoc index e682f7372f817..6c309d56f2294 100644 --- a/docs/user/management.asciidoc +++ b/docs/user/management.asciidoc @@ -167,10 +167,6 @@ set the timespan for notification messages, and much more. the full list of features that are included in your license, see the https://www.elastic.co/subscriptions[subscription page]. -| <> -| Identify the issues that you need to address before upgrading to the -next major version of {es}, and then reindex, if needed. - |=== @@ -197,6 +193,4 @@ include::{kib-repo-dir}/spaces/index.asciidoc[] include::{kib-repo-dir}/management/managing-tags.asciidoc[] -include::{kib-repo-dir}/management/upgrade-assistant/index.asciidoc[] - include::{kib-repo-dir}/management/watcher-ui/index.asciidoc[] diff --git a/docs/user/whats-new.asciidoc b/docs/user/whats-new.asciidoc index 587f4588bb442..640a824180480 100644 --- a/docs/user/whats-new.asciidoc +++ b/docs/user/whats-new.asciidoc @@ -2,9 +2,7 @@ == What's new in 8.0 This section summarizes the most important changes in each release. For the -full list, see <> and <>. - -coming[8.0.0] +full list, see <> and <>. //NOTE: The notable-highlights tagged regions are re-used in the //Installation and Upgrade Guide diff --git a/package.json b/package.json index 8738fdb8e0f9f..f7e49c48660fe 100644 --- a/package.json +++ b/package.json @@ -144,6 +144,7 @@ "@kbn/interpreter": "link:bazel-bin/packages/kbn-interpreter", "@kbn/io-ts-utils": "link:bazel-bin/packages/kbn-io-ts-utils", "@kbn/logging": "link:bazel-bin/packages/kbn-logging", + "@kbn/logging-mocks": "link:bazel-bin/packages/kbn-logging-mocks", "@kbn/mapbox-gl": "link:bazel-bin/packages/kbn-mapbox-gl", "@kbn/monaco": "link:bazel-bin/packages/kbn-monaco", "@kbn/react-field": "link:bazel-bin/packages/kbn-react-field", @@ -216,7 +217,7 @@ "constate": "^1.3.2", "content-disposition": "0.5.3", "copy-to-clipboard": "^3.0.8", - "core-js": "^3.20.3", + "core-js": "^3.21.0", "cronstrue": "^1.51.0", "cytoscape": "^3.10.0", "cytoscape-dagre": "^2.2.2", @@ -231,7 +232,7 @@ "deep-freeze-strict": "^1.1.1", "deepmerge": "^4.2.2", "del": "^5.1.0", - "elastic-apm-node": "^3.27.0", + "elastic-apm-node": "^3.28.0", "execa": "^4.0.2", "exit-hook": "^2.2.0", "expiry-js": "0.1.7", @@ -454,6 +455,7 @@ "@elastic/synthetics": "^1.0.0-beta.16", "@emotion/babel-preset-css-prop": "^11.2.0", "@emotion/jest": "^11.3.0", + "@istanbuljs/nyc-config-typescript": "^1.0.2", "@istanbuljs/schema": "^0.1.2", "@jest/console": "^26.6.2", "@jest/reporters": "^26.6.2", @@ -586,6 +588,8 @@ "@types/kbn__i18n-react": "link:bazel-bin/packages/kbn-i18n-react/npm_module_types", "@types/kbn__interpreter": "link:bazel-bin/packages/kbn-interpreter/npm_module_types", "@types/kbn__io-ts-utils": "link:bazel-bin/packages/kbn-io-ts-utils/npm_module_types", + "@types/kbn__logging": "link:bazel-bin/packages/kbn-logging/npm_module_types", + "@types/kbn__logging-mocks": "link:bazel-bin/packages/kbn-logging-mocks/npm_module_types", "@types/kbn__mapbox-gl": "link:bazel-bin/packages/kbn-mapbox-gl/npm_module_types", "@types/kbn__monaco": "link:bazel-bin/packages/kbn-monaco/npm_module_types", "@types/kbn__optimizer": "link:bazel-bin/packages/kbn-optimizer/npm_module_types", @@ -614,6 +618,7 @@ "@types/kbn__telemetry-tools": "link:bazel-bin/packages/kbn-telemetry-tools/npm_module_types", "@types/kbn__test": "link:bazel-bin/packages/kbn-test/npm_module_types", "@types/kbn__test-jest-helpers": "link:bazel-bin/packages/kbn-test-jest-helpers/npm_module_types", + "@types/kbn__typed-react-router-config": "link:bazel-bin/packages/kbn-typed-react-router-config/npm_module_types", "@types/kbn__ui-shared-deps-npm": "link:bazel-bin/packages/kbn-ui-shared-deps-npm/npm_module_types", "@types/kbn__ui-shared-deps-src": "link:bazel-bin/packages/kbn-ui-shared-deps-src/npm_module_types", "@types/kbn__ui-theme": "link:bazel-bin/packages/kbn-ui-theme/npm_module_types", @@ -727,7 +732,7 @@ "babel-plugin-require-context-hook": "^1.0.0", "babel-plugin-styled-components": "^2.0.2", "babel-plugin-transform-react-remove-prop-types": "^0.4.24", - "backport": "6.1.5", + "backport": "7.0.1", "callsites": "^3.1.0", "chai": "3.5.0", "chance": "1.0.18", @@ -901,4 +906,4 @@ "yargs": "^15.4.1", "zlib": "^1.0.5" } -} \ No newline at end of file +} diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index 7e6d06922aed2..02e82476cd88d 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -33,6 +33,7 @@ filegroup( "//packages/kbn-interpreter:build", "//packages/kbn-io-ts-utils:build", "//packages/kbn-logging:build", + "//packages/kbn-logging-mocks:build", "//packages/kbn-mapbox-gl:build", "//packages/kbn-monaco:build", "//packages/kbn-optimizer:build", @@ -101,6 +102,8 @@ filegroup( "//packages/kbn-i18n-react:build_types", "//packages/kbn-interpreter:build_types", "//packages/kbn-io-ts-utils:build_types", + "//packages/kbn-logging:build_types", + "//packages/kbn-logging-mocks:build_types", "//packages/kbn-mapbox-gl:build_types", "//packages/kbn-monaco:build_types", "//packages/kbn-optimizer:build_types", @@ -129,6 +132,7 @@ filegroup( "//packages/kbn-telemetry-tools:build_types", "//packages/kbn-test:build_types", "//packages/kbn-test-jest-helpers:build_types", + "//packages/kbn-typed-react-router-config:build_types", "//packages/kbn-ui-shared-deps-npm:build_types", "//packages/kbn-ui-shared-deps-src:build_types", "//packages/kbn-ui-theme:build_types", diff --git a/packages/kbn-cli-dev-mode/BUILD.bazel b/packages/kbn-cli-dev-mode/BUILD.bazel index 87d4a116f13b1..133474a3aefa6 100644 --- a/packages/kbn-cli-dev-mode/BUILD.bazel +++ b/packages/kbn-cli-dev-mode/BUILD.bazel @@ -51,7 +51,7 @@ TYPES_DEPS = [ "//packages/kbn-config:npm_module_types", "//packages/kbn-config-schema:npm_module_types", "//packages/kbn-dev-utils:npm_module_types", - "//packages/kbn-logging", + "//packages/kbn-logging:npm_module_types", "//packages/kbn-optimizer:npm_module_types", "//packages/kbn-server-http-tools:npm_module_types", "//packages/kbn-std:npm_module_types", diff --git a/packages/kbn-config/BUILD.bazel b/packages/kbn-config/BUILD.bazel index d7046a26ff92f..0577014768d4c 100644 --- a/packages/kbn-config/BUILD.bazel +++ b/packages/kbn-config/BUILD.bazel @@ -34,6 +34,7 @@ RUNTIME_DEPS = [ "//packages/elastic-safer-lodash-set", "//packages/kbn-config-schema", "//packages/kbn-logging", + "//packages/kbn-logging-mocks", "//packages/kbn-std", "//packages/kbn-utility-types", "//packages/kbn-i18n", @@ -47,7 +48,8 @@ RUNTIME_DEPS = [ TYPES_DEPS = [ "//packages/elastic-safer-lodash-set:npm_module_types", "//packages/kbn-config-schema:npm_module_types", - "//packages/kbn-logging", + "//packages/kbn-logging:npm_module_types", + "//packages/kbn-logging-mocks:npm_module_types", "//packages/kbn-std:npm_module_types", "//packages/kbn-utility-types:npm_module_types", "//packages/kbn-i18n:npm_module_types", diff --git a/packages/kbn-config/src/config_service.test.ts b/packages/kbn-config/src/config_service.test.ts index 32b2d8969d0cc..51e67956637ee 100644 --- a/packages/kbn-config/src/config_service.test.ts +++ b/packages/kbn-config/src/config_service.test.ts @@ -13,7 +13,7 @@ import { mockApplyDeprecations, mockedChangedPaths } from './config_service.test import { rawConfigServiceMock } from './raw/raw_config_service.mock'; import { schema } from '@kbn/config-schema'; -import { MockedLogger, loggerMock } from '@kbn/logging/mocks'; +import { MockedLogger, loggerMock } from '@kbn/logging-mocks'; import type { ConfigDeprecationContext } from './deprecation'; import { ConfigService, Env, RawPackageInfo } from '.'; diff --git a/packages/kbn-logging-mocks/BUILD.bazel b/packages/kbn-logging-mocks/BUILD.bazel new file mode 100644 index 0000000000000..74fb9c2651e5d --- /dev/null +++ b/packages/kbn-logging-mocks/BUILD.bazel @@ -0,0 +1,106 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") + +PKG_BASE_NAME = "kbn-logging-mocks" +PKG_REQUIRE_NAME = "@kbn/logging-mocks" +TYPES_PKG_REQUIRE_NAME = "@types/kbn__logging-mocks" + +SOURCE_FILES = glob( + [ + "src/**/*.ts", + ], + exclude = [ + "**/*.test.*" + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", +] + +RUNTIME_DEPS = [ + "//packages/kbn-logging" +] + +TYPES_DEPS = [ + "//packages/kbn-logging:npm_module_types", + "@npm//@types/jest", + "@npm//@types/node", +] + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + declaration_map = True, + emit_declaration_only = True, + out_dir = "target_types", + source_map = True, + root_dir = "src", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_BASE_NAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [ + ":%s" % PKG_BASE_NAME, + ] +) + +filegroup( + name = "build", + srcs = [ + ":npm_module", + ], + visibility = ["//visibility:public"], +) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = TYPES_PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [ + ":npm_module_types", + ], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-logging-mocks/package.json b/packages/kbn-logging-mocks/package.json new file mode 100644 index 0000000000000..789ffe4500bce --- /dev/null +++ b/packages/kbn-logging-mocks/package.json @@ -0,0 +1,7 @@ +{ + "name": "@kbn/logging-mocks", + "version": "1.0.0", + "private": true, + "license": "SSPL-1.0 OR Elastic License 2.0", + "main": "./target_node/index.js" +} \ No newline at end of file diff --git a/packages/kbn-logging/src/mocks/index.ts b/packages/kbn-logging-mocks/src/index.ts similarity index 100% rename from packages/kbn-logging/src/mocks/index.ts rename to packages/kbn-logging-mocks/src/index.ts diff --git a/packages/kbn-logging/src/mocks/logger.mock.ts b/packages/kbn-logging-mocks/src/logger.mock.ts similarity index 97% rename from packages/kbn-logging/src/mocks/logger.mock.ts rename to packages/kbn-logging-mocks/src/logger.mock.ts index 1b9cdcf71bfa1..b5f1f409ee457 100644 --- a/packages/kbn-logging/src/mocks/logger.mock.ts +++ b/packages/kbn-logging-mocks/src/logger.mock.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { Logger } from '../logger'; +import { Logger } from '@kbn/logging'; export type MockedLogger = jest.Mocked & { context: string[] }; diff --git a/packages/kbn-logging-mocks/tsconfig.json b/packages/kbn-logging-mocks/tsconfig.json new file mode 100644 index 0000000000000..ce53e016c2830 --- /dev/null +++ b/packages/kbn-logging-mocks/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../../tsconfig.bazel.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "target_types", + "rootDir": "src", + "sourceMap": true, + "sourceRoot": "../../../../packages/kbn-logging-mocks/src", + "stripInternal": false, + "types": [ + "jest", + "node" + ] + }, + "include": [ + "src/**/*.ts" + ] +} diff --git a/packages/kbn-logging/BUILD.bazel b/packages/kbn-logging/BUILD.bazel index 8e55456069ee4..09ff3f0d83b2d 100644 --- a/packages/kbn-logging/BUILD.bazel +++ b/packages/kbn-logging/BUILD.bazel @@ -1,9 +1,10 @@ -load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") -load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") -load("//src/dev/bazel:index.bzl", "jsts_transpiler") +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") PKG_BASE_NAME = "kbn-logging" PKG_REQUIRE_NAME = "@kbn/logging" +TYPES_PKG_REQUIRE_NAME = "@types/kbn__logging" SOURCE_FILES = glob( [ @@ -22,7 +23,6 @@ filegroup( ) NPM_MODULE_EXTRA_FILES = [ - "mocks/package.json", "package.json", "README.md" ] @@ -69,7 +69,7 @@ ts_project( js_library( name = PKG_BASE_NAME, srcs = NPM_MODULE_EXTRA_FILES, - deps = RUNTIME_DEPS + [":target_node", ":tsc_types"], + deps = RUNTIME_DEPS + [":target_node"], package_name = PKG_REQUIRE_NAME, visibility = ["//visibility:public"], ) @@ -88,3 +88,20 @@ filegroup( ], visibility = ["//visibility:public"], ) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = TYPES_PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [ + ":npm_module_types", + ], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-logging/mocks/package.json b/packages/kbn-logging/mocks/package.json deleted file mode 100644 index 8410f557e9524..0000000000000 --- a/packages/kbn-logging/mocks/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "private": true, - "main": "../target_node/mocks/index.js", - "types": "../target_types/mocks/index.d.ts" -} \ No newline at end of file diff --git a/packages/kbn-logging/package.json b/packages/kbn-logging/package.json index c35c2f5d06095..0220da8709d30 100644 --- a/packages/kbn-logging/package.json +++ b/packages/kbn-logging/package.json @@ -3,6 +3,5 @@ "version": "1.0.0", "private": true, "license": "SSPL-1.0 OR Elastic License 2.0", - "main": "./target_node/index.js", - "types": "./target_types/index.d.ts" + "main": "./target_node/index.js" } \ No newline at end of file diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 3a7c50feb38b7..f786d01232227 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -116,7 +116,6 @@ pageLoadAssetSize: dataViewManagement: 5000 reporting: 57003 visTypeHeatmap: 25340 - screenshotting: 17017 expressionGauge: 25000 controls: 34788 expressionPartitionVis: 26338 diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index d0fe0f269f6fa..a4b6f4938ddcd 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -51510,7 +51510,7 @@ async function sortPackageJson(kbn) { await fs_promises__WEBPACK_IMPORTED_MODULE_0___default.a.writeFile(packageJsonPath, JSON.stringify(sort_package_json__WEBPACK_IMPORTED_MODULE_1___default()(JSON.parse(packageJson), { // top level keys in the order they were written when this was implemented sortOrder: ['name', 'description', 'keywords', 'private', 'version', 'branch', 'types', 'tsdocMetadata', 'build', 'homepage', 'bugs', 'kibana', 'author', 'scripts', 'repository', 'engines', 'resolutions'] - }), null, 2)); + }), null, 2) + '\n'); } /***/ }), diff --git a/packages/kbn-pm/src/utils/sort_package_json.ts b/packages/kbn-pm/src/utils/sort_package_json.ts index 0abab6f0f89b4..b4df5355744f1 100644 --- a/packages/kbn-pm/src/utils/sort_package_json.ts +++ b/packages/kbn-pm/src/utils/sort_package_json.ts @@ -42,6 +42,6 @@ export async function sortPackageJson(kbn: Kibana) { }), null, 2 - ) + ) + '\n' ); } diff --git a/packages/kbn-typed-react-router-config/BUILD.bazel b/packages/kbn-typed-react-router-config/BUILD.bazel index 6f4e53e58fff7..62fd6adf5bb26 100644 --- a/packages/kbn-typed-react-router-config/BUILD.bazel +++ b/packages/kbn-typed-react-router-config/BUILD.bazel @@ -1,9 +1,10 @@ -load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") -load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") -load("//src/dev/bazel:index.bzl", "jsts_transpiler") +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") PKG_BASE_NAME = "kbn-typed-react-router-config" PKG_REQUIRE_NAME = "@kbn/typed-react-router-config" +TYPES_PKG_REQUIRE_NAME = "@types/kbn__typed-react-router-config" SOURCE_FILES = glob( [ @@ -28,23 +29,30 @@ NPM_MODULE_EXTRA_FILES = [ RUNTIME_DEPS = [ "//packages/kbn-io-ts-utils", - "@npm//tslib", - "@npm//utility-types", + "@npm//fp-ts", + "@npm//history", "@npm//io-ts", + "@npm//lodash", "@npm//query-string", + "@npm//react", "@npm//react-router-config", "@npm//react-router-dom", + "@npm//tslib", + "@npm//utility-types", ] TYPES_DEPS = [ "//packages/kbn-io-ts-utils:npm_module_types", + "@npm//fp-ts", "@npm//query-string", "@npm//utility-types", + "@npm//@types/history", "@npm//@types/jest", + "@npm//@types/lodash", "@npm//@types/node", + "@npm//@types/react", "@npm//@types/react-router-config", "@npm//@types/react-router-dom", - "@npm//@types/history", ] jsts_transpiler( @@ -86,7 +94,7 @@ ts_project( js_library( name = PKG_BASE_NAME, srcs = NPM_MODULE_EXTRA_FILES, - deps = RUNTIME_DEPS + [":target_node", ":target_web", ":tsc_types"], + deps = RUNTIME_DEPS + [":target_node", ":target_web"], package_name = PKG_REQUIRE_NAME, visibility = ["//visibility:public"], ) @@ -105,3 +113,20 @@ filegroup( ], visibility = ["//visibility:public"], ) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = TYPES_PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [ + ":npm_module_types", + ], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-typed-react-router-config/package.json b/packages/kbn-typed-react-router-config/package.json index 50c2e4b5d7e89..0f45f63f4ab2d 100644 --- a/packages/kbn-typed-react-router-config/package.json +++ b/packages/kbn-typed-react-router-config/package.json @@ -1,7 +1,6 @@ { "name": "@kbn/typed-react-router-config", "main": "target_node/index.js", - "types": "target_types/index.d.ts", "browser": "target_web/index.js", "version": "1.0.0", "license": "SSPL-1.0 OR Elastic License 2.0", diff --git a/scripts/functional_tests.js b/scripts/functional_tests.js index b286cf05a6d71..6cfd1d57e9c96 100644 --- a/scripts/functional_tests.js +++ b/scripts/functional_tests.js @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -// eslint-disable-next-line no-restricted-syntax -const alwaysImportedTests = [ +require('../src/setup_node_env'); +require('@kbn/test').runTestsCli([ require.resolve('../test/functional/config.js'), require.resolve('../test/plugin_functional/config.ts'), require.resolve('../test/ui_capabilities/newsfeed_err/config.ts'), @@ -25,18 +25,7 @@ const alwaysImportedTests = [ require.resolve( '../test/interactive_setup_functional/manual_configuration_without_tls.config.ts' ), -]; -// eslint-disable-next-line no-restricted-syntax -const onlyNotInCoverageTests = [ require.resolve('../test/api_integration/config.js'), require.resolve('../test/interpreter_functional/config.ts'), require.resolve('../test/examples/config.js'), -]; - -require('../src/setup_node_env'); -require('@kbn/test').runTestsCli([ - // eslint-disable-next-line no-restricted-syntax - ...alwaysImportedTests, - // eslint-disable-next-line no-restricted-syntax - ...(!!process.env.CODE_COVERAGE ? [] : onlyNotInCoverageTests), ]); diff --git a/src/core/public/application/application_service.tsx b/src/core/public/application/application_service.tsx index 9f5470a2d248e..3010a781b4e9e 100644 --- a/src/core/public/application/application_service.tsx +++ b/src/core/public/application/application_service.tsx @@ -67,8 +67,9 @@ const getAppUrl = (mounters: Map, appId: string, path: string = return appendAppPath(appBasePath, path); }; -const getAppDeepLinkPath = (mounters: Map, appId: string, deepLinkId: string) => { - return mounters.get(appId)?.deepLinkPaths[deepLinkId]; +const getAppDeepLinkPath = (app: App, appId: string, deepLinkId: string) => { + const flattenedLinks = flattenDeepLinks(app.deepLinks); + return flattenedLinks[deepLinkId]; }; const allApplicationsFilter = '__ALL__'; @@ -182,7 +183,6 @@ export class ApplicationService { this.mounters.set(app.id, { appRoute: app.appRoute!, appBasePath: basePath.prepend(app.appRoute!), - deepLinkPaths: toDeepLinkPaths(app.deepLinks), exactRoute: app.exactRoute ?? false, mount: wrapMount(plugin, app), unmountBeforeMounting: false, @@ -242,15 +242,17 @@ export class ApplicationService { ? true : await this.shouldNavigate(overlays, appId); + const targetApp = applications$.value.get(appId); + if (shouldNavigate) { - if (deepLinkId) { - const deepLinkPath = getAppDeepLinkPath(availableMounters, appId, deepLinkId); + if (deepLinkId && targetApp) { + const deepLinkPath = getAppDeepLinkPath(targetApp, appId, deepLinkId); if (deepLinkPath) { path = appendAppPath(deepLinkPath, path); } } if (path === undefined) { - path = applications$.value.get(appId)?.defaultPath; + path = targetApp?.defaultPath; } if (openInNewTab) { this.openInNewTab!(getAppUrl(availableMounters, appId, path)); @@ -290,8 +292,9 @@ export class ApplicationService { deepLinkId, }: { path?: string; absolute?: boolean; deepLinkId?: string } = {} ) => { - if (deepLinkId) { - const deepLinkPath = getAppDeepLinkPath(availableMounters, appId, deepLinkId); + const targetApp = applications$.value.get(appId); + if (deepLinkId && targetApp) { + const deepLinkPath = getAppDeepLinkPath(targetApp, appId, deepLinkId); if (deepLinkPath) { path = appendAppPath(deepLinkPath, path); } @@ -439,12 +442,12 @@ const populateDeepLinkDefaults = (deepLinks?: AppDeepLink[]): AppDeepLink[] => { })); }; -const toDeepLinkPaths = (deepLinks?: AppDeepLink[]): Mounter['deepLinkPaths'] => { +const flattenDeepLinks = (deepLinks?: AppDeepLink[]): Record => { if (!deepLinks) { return {}; } - return deepLinks.reduce((deepLinkPaths: Mounter['deepLinkPaths'], deepLink) => { + return deepLinks.reduce((deepLinkPaths: Record, deepLink) => { if (deepLink.path) deepLinkPaths[deepLink.id] = deepLink.path; - return { ...deepLinkPaths, ...toDeepLinkPaths(deepLink.deepLinks) }; + return { ...deepLinkPaths, ...flattenDeepLinks(deepLink.deepLinks) }; }, {}); }; diff --git a/src/core/public/application/integration_tests/application_service.test.tsx b/src/core/public/application/integration_tests/application_service.test.tsx index 59ef1346ef323..dda029c66f4c3 100644 --- a/src/core/public/application/integration_tests/application_service.test.tsx +++ b/src/core/public/application/integration_tests/application_service.test.tsx @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { BehaviorSubject } from 'rxjs'; import { take } from 'rxjs/operators'; import { act } from 'react-dom/test-utils'; import { createMemoryHistory, MemoryHistory } from 'history'; @@ -16,7 +17,7 @@ import { httpServiceMock } from '../../http/http_service.mock'; import { MockLifecycle } from '../test_types'; import { overlayServiceMock } from '../../overlays/overlay_service.mock'; import { themeServiceMock } from '../../theme/theme_service.mock'; -import { AppMountParameters } from '../types'; +import { AppMountParameters, AppUpdater } from '../types'; import { Observable } from 'rxjs'; import { MountPoint } from 'kibana/public'; @@ -134,6 +135,42 @@ describe('ApplicationService', () => { expect(history.entries.map((entry) => entry.pathname)).toEqual(['/', '/app/app1/bar']); }); + + it('handles updated deepLinks', async () => { + const { register } = service.setup(setupDeps); + + const updater$ = new BehaviorSubject(() => ({})); + + register(Symbol(), { + id: 'app1', + title: 'App1', + deepLinks: [], + updater$, + mount: async ({}: AppMountParameters) => { + return () => undefined; + }, + }); + + const { navigateToApp } = await service.start(startDeps); + + updater$.next(() => ({ + deepLinks: [ + { + id: 'deepLink', + title: 'Some deep link', + path: '/deep-link', + }, + ], + })); + + await navigateToApp('app1', { deepLinkId: 'deepLink' }); + + expect(history.entries.map((entry) => entry.pathname)).toEqual([ + '/', + '/app/app1/deep-link', + ]); + }); + //// }); }); diff --git a/src/core/public/application/integration_tests/utils.tsx b/src/core/public/application/integration_tests/utils.tsx index 7ebc12deccd02..9c1969d9f39c7 100644 --- a/src/core/public/application/integration_tests/utils.tsx +++ b/src/core/public/application/integration_tests/utils.tsx @@ -40,14 +40,12 @@ export const createAppMounter = ({ appId, html = `
App ${appId}
`, appRoute = `/app/${appId}`, - deepLinkPaths = {}, exactRoute = false, extraMountHook, }: { appId: string; html?: string; appRoute?: string; - deepLinkPaths?: Record; exactRoute?: boolean; extraMountHook?: (params: AppMountParameters) => void; }): MockedMounterTuple => { @@ -58,7 +56,6 @@ export const createAppMounter = ({ mounter: { appRoute, appBasePath: appRoute, - deepLinkPaths, exactRoute, mount: jest.fn(async (params: AppMountParameters) => { const { appBasePath: basename, element } = params; diff --git a/src/core/public/application/types.ts b/src/core/public/application/types.ts index 9c3294086efcc..187cee8d0a29a 100644 --- a/src/core/public/application/types.ts +++ b/src/core/public/application/types.ts @@ -636,7 +636,6 @@ export interface AppLeaveActionFactory { export interface Mounter { appRoute: string; appBasePath: string; - deepLinkPaths: Record; mount: AppMount; exactRoute: boolean; unmountBeforeMounting?: boolean; diff --git a/src/core/public/application/ui/app_container.test.tsx b/src/core/public/application/ui/app_container.test.tsx index 4434f6c1751e0..5512b4a120714 100644 --- a/src/core/public/application/ui/app_container.test.tsx +++ b/src/core/public/application/ui/app_container.test.tsx @@ -51,7 +51,6 @@ describe('AppContainer', () => { appRoute: '/some-route', unmountBeforeMounting: false, exactRoute: false, - deepLinkPaths: {}, mount: async ({ element }: AppMountParameters) => { await promise; const container = document.createElement('div'); @@ -67,7 +66,6 @@ describe('AppContainer', () => { appRoute: '/some-route', unmountBeforeMounting: false, exactRoute: false, - deepLinkPaths: {}, mount: jest.fn().mockImplementation(({ element }) => { const container = document.createElement('div'); container.innerHTML = 'some-content'; @@ -190,7 +188,6 @@ describe('AppContainer', () => { const mounter = { appBasePath: '/base-path/some-route', appRoute: '/some-route', - deepLinkPaths: {}, unmountBeforeMounting: false, exactRoute: false, mount: async ({ element }: AppMountParameters) => { diff --git a/src/core/public/http/external_url_service.test.ts b/src/core/public/http/external_url_service.test.ts index ee757c5046760..4ce3709ff6366 100644 --- a/src/core/public/http/external_url_service.test.ts +++ b/src/core/public/http/external_url_service.test.ts @@ -73,6 +73,23 @@ const internalRequestScenarios = [ ]; describe('External Url Service', () => { + describe('#isInternalUrl', () => { + const { setup } = setupService({ + location: new URL('https://example.com/app/management?q=1&bar=false#some-hash'), + serverBasePath: '', + policy: [], + }); + + it('internal request', () => { + expect(setup.isInternalUrl('/')).toBeTruthy(); + expect(setup.isInternalUrl('https://example.com/')).toBeTruthy(); + }); + + it('external request', () => { + expect(setup.isInternalUrl('https://elastic.co/')).toBeFalsy(); + }); + }); + describe('#validateUrl', () => { describe('internal requests with a server base path', () => { const serverBasePath = '/base-path'; diff --git a/src/core/public/http/external_url_service.ts b/src/core/public/http/external_url_service.ts index 166e167b3b994..0fb1c85d48257 100644 --- a/src/core/public/http/external_url_service.ts +++ b/src/core/public/http/external_url_service.ts @@ -50,20 +50,33 @@ function normalizeProtocol(protocol: string) { return protocol.endsWith(':') ? protocol.slice(0, -1).toLowerCase() : protocol.toLowerCase(); } +const createIsInternalUrlValidation = ( + location: Pick, + serverBasePath: string +) => { + return function isInternallUrl(next: string) { + const base = new URL(location.href); + const url = new URL(next, base); + + return ( + url.origin === base.origin && + (!serverBasePath || url.pathname.startsWith(`${serverBasePath}/`)) + ); + }; +}; + const createExternalUrlValidation = ( rules: IExternalUrlPolicy[], location: Pick, serverBasePath: string ) => { + const isInternalUrl = createIsInternalUrlValidation(location, serverBasePath); + return function validateExternalUrl(next: string) { const base = new URL(location.href); const url = new URL(next, base); - const isInternalURL = - url.origin === base.origin && - (!serverBasePath || url.pathname.startsWith(`${serverBasePath}/`)); - - if (isInternalURL) { + if (isInternalUrl(next)) { return url; } @@ -90,6 +103,7 @@ export class ExternalUrlService implements CoreService { const { policy } = injectedMetadata.getExternalUrlConfig(); return { + isInternalUrl: createIsInternalUrlValidation(location, serverBasePath), validateUrl: createExternalUrlValidation(policy, location, serverBasePath), }; } diff --git a/src/core/public/http/http_service.mock.ts b/src/core/public/http/http_service.mock.ts index fff99d84a76a6..bfd81a1003736 100644 --- a/src/core/public/http/http_service.mock.ts +++ b/src/core/public/http/http_service.mock.ts @@ -36,6 +36,7 @@ const createServiceMock = ({ isAnonymous: jest.fn(), }, externalUrl: { + isInternalUrl: jest.fn(), validateUrl: jest.fn(), }, addLoadingCountSource: jest.fn(), diff --git a/src/core/public/http/types.ts b/src/core/public/http/types.ts index 876799765ea1c..afe1d653c599c 100644 --- a/src/core/public/http/types.ts +++ b/src/core/public/http/types.ts @@ -110,6 +110,13 @@ export interface IBasePath { * @public */ export interface IExternalUrl { + /** + * Determines if the provided URL is an internal url. + * + * @param relativeOrAbsoluteUrl + */ + isInternalUrl(relativeOrAbsoluteUrl: string): boolean; + /** * Determines if the provided URL is a valid location to send users. * Validation is based on the configured allow list in kibana.yml. diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index db132e267807e..d8cf4706ceb16 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -686,6 +686,7 @@ export interface IBasePath { // @public export interface IExternalUrl { + isInternalUrl(relativeOrAbsoluteUrl: string): boolean; validateUrl(relativeOrAbsoluteUrl: string): URL | null; } diff --git a/src/core/server/http/integration_tests/router.test.ts b/src/core/server/http/integration_tests/router.test.ts index 1bc42a556dbc4..57403aff5d7eb 100644 --- a/src/core/server/http/integration_tests/router.test.ts +++ b/src/core/server/http/integration_tests/router.test.ts @@ -17,7 +17,7 @@ import { loggingSystemMock } from '../../logging/logging_system.mock'; import { createHttpServer } from '../test_utils'; import { HttpService } from '../http_service'; import { Router } from '../router'; -import { loggerMock } from '@kbn/logging/mocks'; +import { loggerMock } from '@kbn/logging-mocks'; let server: HttpService; let logger: ReturnType; diff --git a/src/core/server/logging/logger.mock.ts b/src/core/server/logging/logger.mock.ts index cfabaeb72adf7..55ce55bc035e4 100644 --- a/src/core/server/logging/logger.mock.ts +++ b/src/core/server/logging/logger.mock.ts @@ -6,5 +6,5 @@ * Side Public License, v 1. */ -export { loggerMock } from '@kbn/logging/mocks'; -export type { MockedLogger } from '@kbn/logging/mocks'; +export { loggerMock } from '@kbn/logging-mocks'; +export type { MockedLogger } from '@kbn/logging-mocks'; diff --git a/src/core/server/metrics/collectors/cgroup.test.ts b/src/core/server/metrics/collectors/cgroup.test.ts index 269437f026f2f..3f12107c80ea1 100644 --- a/src/core/server/metrics/collectors/cgroup.test.ts +++ b/src/core/server/metrics/collectors/cgroup.test.ts @@ -7,7 +7,7 @@ */ import mockFs from 'mock-fs'; -import { loggerMock } from '@kbn/logging/mocks'; +import { loggerMock } from '@kbn/logging-mocks'; import { OsCgroupMetricsCollector } from './cgroup'; describe('OsCgroupMetricsCollector', () => { diff --git a/src/core/server/metrics/collectors/os.test.ts b/src/core/server/metrics/collectors/os.test.ts index 5592038f1416a..4715fab16cb74 100644 --- a/src/core/server/metrics/collectors/os.test.ts +++ b/src/core/server/metrics/collectors/os.test.ts @@ -8,7 +8,7 @@ jest.mock('getos', () => (cb: Function) => cb(null, { dist: 'distrib', release: 'release' })); -import { loggerMock } from '@kbn/logging/mocks'; +import { loggerMock } from '@kbn/logging-mocks'; import os from 'os'; import { cgroupCollectorMock } from './os.test.mocks'; import { OsMetricsCollector } from './os'; diff --git a/src/core/server/metrics/ops_metrics_collector.test.ts b/src/core/server/metrics/ops_metrics_collector.test.ts index 7d263d8b7d6af..78160729f7bdc 100644 --- a/src/core/server/metrics/ops_metrics_collector.test.ts +++ b/src/core/server/metrics/ops_metrics_collector.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { loggerMock } from '@kbn/logging/mocks'; +import { loggerMock } from '@kbn/logging-mocks'; import { mockOsCollector, mockProcessCollector, diff --git a/src/dev/build/tasks/download_cloud_dependencies.ts b/src/dev/build/tasks/download_cloud_dependencies.ts index 1207594304e64..6ecc09c21ddce 100644 --- a/src/dev/build/tasks/download_cloud_dependencies.ts +++ b/src/dev/build/tasks/download_cloud_dependencies.ts @@ -36,12 +36,19 @@ export const DownloadCloudDependencies: Task = { let buildId = ''; if (!config.isRelease) { - const manifest = await Axios.get( - `https://artifacts-api.elastic.co/v1/versions/${config.getBuildVersion()}/builds/latest` - ); - buildId = manifest.data.build.build_id; + const manifestUrl = `https://artifacts-api.elastic.co/v1/versions/${config.getBuildVersion()}/builds/latest`; + try { + const manifest = await Axios.get(manifestUrl); + buildId = manifest.data.build.build_id; + } catch (e) { + log.error( + `Unable to find Elastic artifacts for ${config.getBuildVersion()} at ${manifestUrl}.` + ); + throw e; + } } await del([config.resolveFromRepo('.beats')]); + await downloadBeat('metricbeat', buildId); await downloadBeat('filebeat', buildId); }, diff --git a/src/dev/code_coverage/nyc_config/nyc.functional.config.js b/src/dev/code_coverage/nyc_config/nyc.functional.config.js index 479c40ec9e109..1b68c23db5f4b 100644 --- a/src/dev/code_coverage/nyc_config/nyc.functional.config.js +++ b/src/dev/code_coverage/nyc_config/nyc.functional.config.js @@ -7,7 +7,18 @@ */ const defaultExclude = require('@istanbuljs/schema/default-exclude'); -const extraExclude = ['data/optimize/**', 'src/core/server/**', '**/{test, types}/**/*']; +const extraExclude = [ + 'data/optimize/**', + '**/{__jest__,__test__,__examples__,__fixtures__,__snapshots__,__stories__,*mock*,*storybook,target,types}/**/*', + '**/{integration_tests,test,tests,test_helpers,test_data,test_samples,test_utils,test_utilities,*scripts}/**/*', + '**/{*e2e*,fixtures,manual_tests,stub*}/**', + '**/*mock*.{ts,tsx}', + '**/*.test.{ts,tsx}', + '**/*.spec.{ts,tsx}', + '**/types.ts', + '**/*.d.ts', + '**/index.{js,ts,tsx}', +]; const path = require('path'); module.exports = { @@ -16,5 +27,9 @@ module.exports = { : 'target/kibana-coverage/functional', 'report-dir': 'target/kibana-coverage/functional-combined', reporter: ['html', 'json-summary'], + include: [ + 'src/{core,plugins}/**/*.{js,mjs,jsx,ts,tsx}', + 'x-pack/plugins/**/*.{js,mjs,jsx,ts,tsx}', + ], exclude: extraExclude.concat(defaultExclude), }; diff --git a/src/dev/code_coverage/nyc_config/nyc.server.config.js b/src/dev/code_coverage/nyc_config/nyc.server.config.js new file mode 100644 index 0000000000000..d8cebf468d0db --- /dev/null +++ b/src/dev/code_coverage/nyc_config/nyc.server.config.js @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +const path = require('path'); + +module.exports = { + extends: '@istanbuljs/nyc-config-typescript', + 'report-dir': process.env.KIBANA_DIR + ? path.resolve(process.env.KIBANA_DIR, 'target/kibana-coverage/server') + : 'target/kibana-coverage/server', + reporter: ['json'], + all: true, + include: [ + 'src/{core,plugins}/**/*.{js,mjs,jsx,ts,tsx}', + 'x-pack/plugins/**/*.{js,mjs,jsx,ts,tsx}', + ], + exclude: [ + '**/{__jest__,__test__,__examples__,__fixtures__,__snapshots__,__stories__,*mock*,*storybook,target,types}/**/*', + '**/{integration_tests,test,tests,test_helpers,test_data,test_samples,test_utils,test_utilities,*scripts}/**/*', + '**/{*e2e*,fixtures,manual_tests,stub*}/**', + '**/*mock*.{ts,tsx}', + '**/*.test.{ts,tsx}', + '**/*.spec.{ts,tsx}', + '**/types.ts', + '**/*.d.ts', + '**/index.{js,ts,tsx}', + ], +}; diff --git a/src/plugins/chart_expressions/expression_gauge/common/expression_functions/__snapshots__/gauge_function.test.ts.snap b/src/plugins/chart_expressions/expression_gauge/common/expression_functions/__snapshots__/gauge_function.test.ts.snap index 1a7ca53228a60..a0b8cddaf4f9c 100644 --- a/src/plugins/chart_expressions/expression_gauge/common/expression_functions/__snapshots__/gauge_function.test.ts.snap +++ b/src/plugins/chart_expressions/expression_gauge/common/expression_functions/__snapshots__/gauge_function.test.ts.snap @@ -6,6 +6,7 @@ Object { "type": "render", "value": Object { "args": Object { + "ariaLabel": undefined, "colorMode": "none", "goalAccessor": undefined, "labelMajor": "title", diff --git a/src/plugins/chart_expressions/expression_gauge/common/expression_functions/gauge_function.ts b/src/plugins/chart_expressions/expression_gauge/common/expression_functions/gauge_function.ts index 2c24aa292319c..61de491595f05 100644 --- a/src/plugins/chart_expressions/expression_gauge/common/expression_functions/gauge_function.ts +++ b/src/plugins/chart_expressions/expression_gauge/common/expression_functions/gauge_function.ts @@ -93,14 +93,27 @@ export const gaugeFunction = (): GaugeExpressionFunctionDefinition => ({ }), required: false, }, + ariaLabel: { + types: ['string'], + help: i18n.translate('expressionGauge.functions.gaugeChart.config.ariaLabel.help', { + defaultMessage: 'Specifies the aria label of the gauge chart', + }), + required: false, + }, }, - fn(data, args) { + fn(data, args, handlers) { return { type: 'render', as: EXPRESSION_GAUGE_NAME, value: { data, - args, + args: { + ...args, + ariaLabel: + args.ariaLabel ?? + (handlers.variables?.embeddableTitle as string) ?? + handlers.getExecutionContext?.()?.description, + }, }, }; }, diff --git a/src/plugins/chart_expressions/expression_gauge/common/types/expression_functions.ts b/src/plugins/chart_expressions/expression_gauge/common/types/expression_functions.ts index 16f246bf24713..e1cebae438758 100644 --- a/src/plugins/chart_expressions/expression_gauge/common/types/expression_functions.ts +++ b/src/plugins/chart_expressions/expression_gauge/common/types/expression_functions.ts @@ -46,6 +46,7 @@ export type GaugeArguments = GaugeState & { shape: GaugeShape; colorMode: GaugeColorMode; palette?: PaletteOutput; + ariaLabel?: string; }; export type GaugeInput = Datatable; diff --git a/src/plugins/chart_expressions/expression_gauge/public/components/__snapshots__/gauge_component.test.tsx.snap b/src/plugins/chart_expressions/expression_gauge/public/components/__snapshots__/gauge_component.test.tsx.snap index bd39344807643..9af3bb2be8a57 100644 --- a/src/plugins/chart_expressions/expression_gauge/public/components/__snapshots__/gauge_component.test.tsx.snap +++ b/src/plugins/chart_expressions/expression_gauge/public/components/__snapshots__/gauge_component.test.tsx.snap @@ -5,6 +5,7 @@ exports[`GaugeComponent renders the chart 1`] = ` renderer="canvas" > diff --git a/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.tsx b/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.tsx index dfd7755c47681..593c18e5e9b05 100644 --- a/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.tsx +++ b/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.tsx @@ -231,7 +231,12 @@ export const GaugeComponent: FC = memo( return ( - + ({ defaultMessage: 'The id of the split column or the corresponding dimension', }), }, + ariaLabel: { + types: ['string'], + help: i18n.translate('expressionHeatmap.functions.args.ariaLabelHelpText', { + defaultMessage: 'Specifies the aria label of the heat map', + }), + required: false, + }, }, fn(data, args, handlers) { if (handlers?.inspectorAdapters?.tables) { @@ -203,7 +210,13 @@ export const heatmapFunction = (): HeatmapExpressionFunctionDefinition => ({ as: EXPRESSION_HEATMAP_NAME, value: { data, - args, + args: { + ...args, + ariaLabel: + args.ariaLabel ?? + (handlers.variables?.embeddableTitle as string) ?? + handlers.getExecutionContext?.()?.description, + }, }, }; }, diff --git a/src/plugins/chart_expressions/expression_heatmap/common/types/expression_functions.ts b/src/plugins/chart_expressions/expression_heatmap/common/types/expression_functions.ts index efd4e1a8b990c..10e43e426317d 100644 --- a/src/plugins/chart_expressions/expression_heatmap/common/types/expression_functions.ts +++ b/src/plugins/chart_expressions/expression_heatmap/common/types/expression_functions.ts @@ -77,6 +77,7 @@ export interface HeatmapArguments { splitColumnAccessor?: string | ExpressionValueVisDimension; legend: HeatmapLegendConfigResult; gridConfig: HeatmapGridConfigResult; + ariaLabel?: string; } export type HeatmapInput = Datatable; diff --git a/src/plugins/chart_expressions/expression_heatmap/public/components/heatmap_component.tsx b/src/plugins/chart_expressions/expression_heatmap/public/components/heatmap_component.tsx index 3c751956c0ea2..c1e026064fdfb 100644 --- a/src/plugins/chart_expressions/expression_heatmap/public/components/heatmap_component.tsx +++ b/src/plugins/chart_expressions/expression_heatmap/public/components/heatmap_component.tsx @@ -521,6 +521,8 @@ export const HeatmapComponent: FC = memo( : NaN, }} onBrushEnd={interactive ? (onBrushEnd as BrushEndListener) : undefined} + ariaLabel={args.ariaLabel} + ariaUseDefaultSummary={!args.ariaLabel} /> = memo( yAxisLabelName={yAxisColumn?.name} xAxisTitle={args.gridConfig.isXAxisTitleVisible ? xAxisTitle : undefined} yAxisTitle={args.gridConfig.isYAxisTitleVisible ? yAxisTitle : undefined} - xAxisLabelFormatter={(v) => `${xValuesFormatter.convert(v) ?? ''}`} + xAxisLabelFormatter={(v) => + args.gridConfig.isXAxisLabelVisible ? `${xValuesFormatter.convert(v)}` : '' + } yAxisLabelFormatter={ yAxisColumn - ? (v) => `${formatFactory(yAxisColumn.meta.params).convert(v) ?? ''}` + ? (v) => + args.gridConfig.isYAxisLabelVisible + ? `${formatFactory(yAxisColumn.meta.params).convert(v) ?? ''}` + : '' : undefined } /> diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/mosaic_vis_function.test.ts.snap b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/mosaic_vis_function.test.ts.snap index de064a44058cc..f1bd7834e52f1 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/mosaic_vis_function.test.ts.snap +++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/mosaic_vis_function.test.ts.snap @@ -59,6 +59,7 @@ Object { "syncColors": false, "visConfig": Object { "addTooltip": true, + "ariaLabel": undefined, "buckets": Array [ Object { "accessor": 1, diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/pie_vis_function.test.ts.snap b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/pie_vis_function.test.ts.snap index 95b8df13882d9..d73f53277a2ba 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/pie_vis_function.test.ts.snap +++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/pie_vis_function.test.ts.snap @@ -32,6 +32,7 @@ Object { "syncColors": false, "visConfig": Object { "addTooltip": true, + "ariaLabel": undefined, "buckets": Array [ Object { "accessor": 1, @@ -164,6 +165,7 @@ Object { "syncColors": false, "visConfig": Object { "addTooltip": true, + "ariaLabel": undefined, "buckets": Array [ Object { "accessor": 1, diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/treemap_vis_function.test.ts.snap b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/treemap_vis_function.test.ts.snap index d18dca573606a..b8d8032fa5839 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/treemap_vis_function.test.ts.snap +++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/treemap_vis_function.test.ts.snap @@ -59,6 +59,7 @@ Object { "syncColors": false, "visConfig": Object { "addTooltip": true, + "ariaLabel": undefined, "buckets": Array [ Object { "accessor": 1, diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/waffle_vis_function.test.ts.snap b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/waffle_vis_function.test.ts.snap index 54ead941c7548..7c6922cdff84a 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/waffle_vis_function.test.ts.snap +++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/waffle_vis_function.test.ts.snap @@ -59,6 +59,7 @@ Object { "syncColors": false, "visConfig": Object { "addTooltip": true, + "ariaLabel": undefined, "bucket": Object { "accessor": 1, "format": Object { diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/i18n.ts b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/i18n.ts index 0be470121ecb4..aa433b8eaee2d 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/i18n.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/i18n.ts @@ -93,6 +93,10 @@ export const strings = { i18n.translate('expressionPartitionVis.waffle.function.args.showValuesInLegendHelpText', { defaultMessage: 'Show values in legend', }), + getAriaLabelHelp: () => + i18n.translate('expressionPartitionVis.reusable.functions.args.ariaLabelHelpText', { + defaultMessage: 'Specifies the aria label of the chart', + }), getSliceSizeHelp: () => i18n.translate('expressionPartitionVis.reusable.function.dimension.metric', { diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/mosaic_vis_function.ts b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/mosaic_vis_function.ts index 388b0741d23d3..142bc6290d476 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/mosaic_vis_function.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/mosaic_vis_function.ts @@ -82,6 +82,11 @@ export const mosaicVisFunction = (): MosaicVisExpressionFunctionDefinition => ({ help: strings.getLabelsArgHelp(), default: `{${PARTITION_LABELS_FUNCTION}}`, }, + ariaLabel: { + types: ['string'], + help: strings.getAriaLabelHelp(), + required: false, + }, }, fn(context, args, handlers) { const maxSupportedBuckets = 2; @@ -95,6 +100,10 @@ export const mosaicVisFunction = (): MosaicVisExpressionFunctionDefinition => ({ const visConfig: PartitionVisParams = { ...args, + ariaLabel: + args.ariaLabel ?? + (handlers.variables?.embeddableTitle as string) ?? + handlers.getExecutionContext?.()?.description, palette: args.palette, dimensions: { metric: args.metric, diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/pie_vis_function.ts b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/pie_vis_function.ts index c054d572538ce..80302f877698c 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/pie_vis_function.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/pie_vis_function.ts @@ -109,6 +109,11 @@ export const pieVisFunction = (): PieVisExpressionFunctionDefinition => ({ help: strings.getStartFromSecondLargestSliceArgHelp(), default: true, }, + ariaLabel: { + types: ['string'], + help: strings.getAriaLabelHelp(), + required: false, + }, }, fn(context, args, handlers) { if (args.splitColumn && args.splitRow) { @@ -117,6 +122,10 @@ export const pieVisFunction = (): PieVisExpressionFunctionDefinition => ({ const visConfig: PartitionVisParams = { ...args, + ariaLabel: + args.ariaLabel ?? + (handlers.variables?.embeddableTitle as string) ?? + handlers.getExecutionContext?.()?.description, palette: args.palette, dimensions: { metric: args.metric, diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/treemap_vis_function.ts b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/treemap_vis_function.ts index d0ae42b4b7942..65f016729eabe 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/treemap_vis_function.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/treemap_vis_function.ts @@ -82,6 +82,11 @@ export const treemapVisFunction = (): TreemapVisExpressionFunctionDefinition => help: strings.getLabelsArgHelp(), default: `{${PARTITION_LABELS_FUNCTION}}`, }, + ariaLabel: { + types: ['string'], + help: strings.getAriaLabelHelp(), + required: false, + }, }, fn(context, args, handlers) { const maxSupportedBuckets = 2; @@ -95,6 +100,10 @@ export const treemapVisFunction = (): TreemapVisExpressionFunctionDefinition => const visConfig: PartitionVisParams = { ...args, + ariaLabel: + args.ariaLabel ?? + (handlers.variables?.embeddableTitle as string) ?? + handlers.getExecutionContext?.()?.description, palette: args.palette, dimensions: { metric: args.metric, diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/waffle_vis_function.ts b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/waffle_vis_function.ts index ade524aad59c8..b1b30539949c4 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/waffle_vis_function.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/waffle_vis_function.ts @@ -81,6 +81,11 @@ export const waffleVisFunction = (): WaffleVisExpressionFunctionDefinition => ({ help: strings.getShowValuesInLegendArgHelp(), default: false, }, + ariaLabel: { + types: ['string'], + help: strings.getAriaLabelHelp(), + required: false, + }, }, fn(context, args, handlers) { if (args.splitColumn && args.splitRow) { @@ -90,6 +95,10 @@ export const waffleVisFunction = (): WaffleVisExpressionFunctionDefinition => ({ const buckets = args.bucket ? [args.bucket] : []; const visConfig: PartitionVisParams = { ...args, + ariaLabel: + args.ariaLabel ?? + (handlers.variables?.embeddableTitle as string) ?? + handlers.getExecutionContext?.()?.description, palette: args.palette, dimensions: { metric: args.metric, diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/types/expression_renderers.ts b/src/plugins/chart_expressions/expression_partition_vis/common/types/expression_renderers.ts index 87358d5dbe659..01ca39c9cbb36 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/common/types/expression_renderers.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/common/types/expression_renderers.ts @@ -52,6 +52,7 @@ interface VisCommonParams { legendPosition: Position; truncateLegend: boolean; maxLegendLines: number; + ariaLabel?: string; } interface VisCommonConfig extends VisCommonParams { diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/components/__snapshots__/partition_vis_component.test.tsx.snap b/src/plugins/chart_expressions/expression_partition_vis/public/components/__snapshots__/partition_vis_component.test.tsx.snap index 4e56d2c5efa4c..b367db1af5437 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/public/components/__snapshots__/partition_vis_component.test.tsx.snap +++ b/src/plugins/chart_expressions/expression_partition_vis/public/components/__snapshots__/partition_vis_component.test.tsx.snap @@ -238,6 +238,7 @@ exports[`PartitionVisComponent should render correct structure for donut 1`] = ` > { ]} baseTheme={chartBaseTheme} onRenderChange={onRenderChange} + ariaLabel={props.visParams.ariaLabel} + ariaUseDefaultSummary={!props.visParams.ariaLabel} /> { types: ['vis_dimension'], help: argHelp.bucket, }, + ariaLabel: { + types: ['string'], + help: argHelp.ariaLabel, + required: false, + }, }, fn(input, args, handlers) { const visParams: TagCloudRendererParams = { @@ -136,6 +144,10 @@ export const tagcloudFunction: ExpressionTagcloudFunction = () => { bucket: args.bucket, }), palette: args.palette, + ariaLabel: + args.ariaLabel ?? + (handlers.variables?.embeddableTitle as string) ?? + handlers.getExecutionContext?.()?.description, }; if (handlers?.inspectorAdapters?.tables) { diff --git a/src/plugins/chart_expressions/expression_tagcloud/common/types/expression_functions.ts b/src/plugins/chart_expressions/expression_tagcloud/common/types/expression_functions.ts index 091b3e861332d..44fc6f3048790 100644 --- a/src/plugins/chart_expressions/expression_tagcloud/common/types/expression_functions.ts +++ b/src/plugins/chart_expressions/expression_tagcloud/common/types/expression_functions.ts @@ -20,6 +20,7 @@ interface TagCloudCommonParams { minFontSize: number; maxFontSize: number; showLabel: boolean; + ariaLabel?: string; } export interface TagCloudVisConfig extends TagCloudCommonParams { diff --git a/src/plugins/chart_expressions/expression_tagcloud/public/components/tagcloud_component.tsx b/src/plugins/chart_expressions/expression_tagcloud/public/components/tagcloud_component.tsx index 2bec25534f49b..560507f84831a 100644 --- a/src/plugins/chart_expressions/expression_tagcloud/public/components/tagcloud_component.tsx +++ b/src/plugins/chart_expressions/expression_tagcloud/public/components/tagcloud_component.tsx @@ -191,7 +191,12 @@ export const TagCloudChart = ({ {(resizeRef) => (
- + { diff --git a/src/plugins/dashboard/kibana.json b/src/plugins/dashboard/kibana.json index 683a1a551f81d..0130d4a5f8118 100644 --- a/src/plugins/dashboard/kibana.json +++ b/src/plugins/dashboard/kibana.json @@ -8,6 +8,7 @@ "version": "kibana", "requiredPlugins": [ "data", + "dataViews", "embeddable", "controls", "inspector", diff --git a/src/plugins/dashboard/public/application/dashboard_router.tsx b/src/plugins/dashboard/public/application/dashboard_router.tsx index ae16527b64440..05d663bdac265 100644 --- a/src/plugins/dashboard/public/application/dashboard_router.tsx +++ b/src/plugins/dashboard/public/application/dashboard_router.tsx @@ -110,7 +110,7 @@ export async function mountApp({ uiSettings: coreStart.uiSettings, scopedHistory: () => scopedHistory, screenshotModeService: screenshotMode, - indexPatterns: dataStart.indexPatterns, + dataViews: dataStart.dataViews, savedQueryService: dataStart.query.savedQueries, savedObjectsClient: coreStart.savedObjects.client, savedDashboards: dashboardStart.getSavedDashboardLoader(), @@ -212,7 +212,7 @@ export async function mountApp({ .getIncomingEmbeddablePackage(DashboardConstants.DASHBOARDS_ID, false) ); if (!hasEmbeddableIncoming) { - dataStart.indexPatterns.clearCache(); + dataStart.dataViews.clearCache(); } // dispatch synthetic hash change event to update hash history objects diff --git a/src/plugins/dashboard/public/application/embeddable/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap b/src/plugins/dashboard/public/application/embeddable/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap index 70a21438754bd..e416dced4f8a1 100644 --- a/src/plugins/dashboard/public/application/embeddable/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap +++ b/src/plugins/dashboard/public/application/embeddable/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap @@ -19,6 +19,7 @@ exports[`DashboardEmptyScreen renders correctly with edit mode 1`] = ` }, "delete": [MockFunction], "externalUrl": Object { + "isInternalUrl": [MockFunction], "validateUrl": [MockFunction], }, "fetch": [MockFunction], @@ -343,6 +344,7 @@ exports[`DashboardEmptyScreen renders correctly with readonly mode 1`] = ` }, "delete": [MockFunction], "externalUrl": Object { + "isInternalUrl": [MockFunction], "validateUrl": [MockFunction], }, "fetch": [MockFunction], @@ -706,6 +708,7 @@ exports[`DashboardEmptyScreen renders correctly with view mode 1`] = ` }, "delete": [MockFunction], "externalUrl": Object { + "isInternalUrl": [MockFunction], "validateUrl": [MockFunction], }, "fetch": [MockFunction], diff --git a/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.test.tsx b/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.test.tsx index 0ef21fca26f29..039a600d153b2 100644 --- a/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.test.tsx +++ b/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.test.tsx @@ -24,13 +24,15 @@ import { EmbeddableFactory, ViewMode } from '../../services/embeddable'; import { dashboardStateStore, setDescription, setViewMode } from '../state'; import { DashboardContainerServices } from '../embeddable/dashboard_container'; import { createKbnUrlStateStorage, defer } from '../../../../kibana_utils/public'; -import { Filter, IIndexPattern, IndexPatternsContract } from '../../services/data'; import { useDashboardAppState, UseDashboardStateProps } from './use_dashboard_app_state'; import { getSampleDashboardInput, getSavedDashboardMock, makeDefaultServices, } from '../test_helpers'; +import { DataViewsContract } from '../../services/data'; +import { DataView } from '../../services/data_views'; +import type { Filter } from '@kbn/es-query'; interface SetupEmbeddableFactoryReturn { finalizeEmbeddableCreation: () => void; @@ -56,12 +58,10 @@ const createDashboardAppStateProps = (): UseDashboardStateProps => ({ const createDashboardAppStateServices = () => { const defaults = makeDefaultServices(); - const indexPatterns = {} as IndexPatternsContract; - const defaultIndexPattern = { id: 'foo', fields: [{ name: 'bar' }] } as IIndexPattern; - indexPatterns.ensureDefaultDataView = jest.fn().mockImplementation(() => Promise.resolve(true)); - indexPatterns.getDefault = jest - .fn() - .mockImplementation(() => Promise.resolve(defaultIndexPattern)); + const dataViews = {} as DataViewsContract; + const defaultDataView = { id: 'foo', fields: [{ name: 'bar' }] } as DataView; + dataViews.ensureDefaultDataView = jest.fn().mockImplementation(() => Promise.resolve(true)); + dataViews.getDefault = jest.fn().mockImplementation(() => Promise.resolve(defaultDataView)); const data = dataPluginMock.createStartContract(); data.query.filterManager.getUpdates$ = jest.fn().mockImplementation(() => of(void 0)); @@ -71,7 +71,7 @@ const createDashboardAppStateServices = () => { .fn() .mockImplementation(() => of(void 0)); - return { ...defaults, indexPatterns, data }; + return { ...defaults, dataViews, data }; }; const setupEmbeddableFactory = ( diff --git a/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.ts b/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.ts index 8c58eab0ded83..98b3d761f350e 100644 --- a/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.ts +++ b/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.ts @@ -15,6 +15,7 @@ import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs'; import { DashboardConstants } from '../..'; import { ViewMode } from '../../services/embeddable'; import { useKibana } from '../../services/kibana_react'; +import { DataView } from '../../services/data_views'; import { getNewDashboardTitle } from '../../dashboard_strings'; import { IKbnUrlStateStorage } from '../../services/kibana_utils'; import { setDashboardState, useDashboardDispatch, useDashboardSelector } from '../state'; @@ -30,7 +31,7 @@ import { tryDestroyDashboardContainer, syncDashboardContainerInput, savedObjectToDashboardState, - syncDashboardIndexPatterns, + syncDashboardDataViews, syncDashboardFilterState, loadSavedDashboardState, buildDashboardContainer, @@ -81,7 +82,7 @@ export const useDashboardAppState = ({ core, chrome, embeddable, - indexPatterns, + dataViews, usageCollection, savedDashboards, initializerContext, @@ -121,7 +122,7 @@ export const useDashboardAppState = ({ search, history, embeddable, - indexPatterns, + dataViews, notifications, kibanaVersion, savedDashboards, @@ -184,7 +185,7 @@ export const useDashboardAppState = ({ // Backwards compatible way of detecting that we are taking a screenshot const legacyPrintLayoutDetected = screenshotModeService?.isScreenshotMode() && - screenshotModeService.getScreenshotLayout() === 'print'; + screenshotModeService.getScreenshotContext('layout') === 'print'; const initialDashboardState = { ...savedDashboardState, @@ -234,11 +235,11 @@ export const useDashboardAppState = ({ /** * Start syncing index patterns between the Query Service and the Dashboard Container. */ - const indexPatternsSubscription = syncDashboardIndexPatterns({ + const dataViewsSubscription = syncDashboardDataViews({ dashboardContainer, - indexPatterns: dashboardBuildContext.indexPatterns, - onUpdateIndexPatterns: (newIndexPatterns) => - setDashboardAppState((s) => ({ ...s, indexPatterns: newIndexPatterns })), + dataViews: dashboardBuildContext.dataViews, + onUpdateDataViews: (newDataViews: DataView[]) => + setDashboardAppState((s) => ({ ...s, dataViews: newDataViews })), }); /** @@ -339,7 +340,7 @@ export const useDashboardAppState = ({ stopWatchingAppStateInUrl(); stopSyncingDashboardFilterState(); lastSavedSubscription.unsubscribe(); - indexPatternsSubscription.unsubscribe(); + dataViewsSubscription.unsubscribe(); tryDestroyDashboardContainer(dashboardContainer); setDashboardAppState((state) => ({ ...state, @@ -368,7 +369,7 @@ export const useDashboardAppState = ({ usageCollection, scopedHistory, notifications, - indexPatterns, + dataViews, kibanaVersion, embeddable, docTitle, diff --git a/src/plugins/dashboard/public/application/lib/build_dashboard_container.ts b/src/plugins/dashboard/public/application/lib/build_dashboard_container.ts index 1dd39cc3e5ba9..5752a6445d2a9 100644 --- a/src/plugins/dashboard/public/application/lib/build_dashboard_container.ts +++ b/src/plugins/dashboard/public/application/lib/build_dashboard_container.ts @@ -31,7 +31,7 @@ import { } from '../../services/embeddable'; type BuildDashboardContainerProps = DashboardBuildContext & { - data: DashboardAppServices['data']; // the whole data service is required here because it is required by getUrlGeneratorState + data: DashboardAppServices['data']; // the whole data service is required here because it is required by getLocatorParams savedDashboard: DashboardSavedObject; initialDashboardState: DashboardState; incomingEmbeddable?: EmbeddablePackageState; diff --git a/src/plugins/dashboard/public/application/lib/dashboard_control_group.ts b/src/plugins/dashboard/public/application/lib/dashboard_control_group.ts index 90d5a67c3da47..0d1eb3537377f 100644 --- a/src/plugins/dashboard/public/application/lib/dashboard_control_group.ts +++ b/src/plugins/dashboard/public/application/lib/dashboard_control_group.ts @@ -8,7 +8,7 @@ import { Subscription } from 'rxjs'; import deepEqual from 'fast-deep-equal'; -import { compareFilters, COMPARE_ALL_OPTIONS, Filter } from '@kbn/es-query'; +import { compareFilters, COMPARE_ALL_OPTIONS, type Filter } from '@kbn/es-query'; import { distinctUntilChanged, distinctUntilKeyChanged } from 'rxjs/operators'; import { DashboardContainer } from '..'; diff --git a/src/plugins/dashboard/public/application/lib/diff_dashboard_state.ts b/src/plugins/dashboard/public/application/lib/diff_dashboard_state.ts index 264c8fcb1de2e..729b0d06f4ab8 100644 --- a/src/plugins/dashboard/public/application/lib/diff_dashboard_state.ts +++ b/src/plugins/dashboard/public/application/lib/diff_dashboard_state.ts @@ -8,7 +8,7 @@ import { xor, omit, isEmpty } from 'lodash'; import fastIsEqual from 'fast-deep-equal'; -import { compareFilters, COMPARE_ALL_OPTIONS, Filter, isFilterPinned } from '@kbn/es-query'; +import { compareFilters, COMPARE_ALL_OPTIONS, type Filter, isFilterPinned } from '@kbn/es-query'; import { DashboardContainerInput } from '../..'; import { controlGroupInputIsEqual } from './dashboard_control_group'; diff --git a/src/plugins/dashboard/public/application/lib/index.ts b/src/plugins/dashboard/public/application/lib/index.ts index 58f962591b67c..eab3604ff841b 100644 --- a/src/plugins/dashboard/public/application/lib/index.ts +++ b/src/plugins/dashboard/public/application/lib/index.ts @@ -18,7 +18,7 @@ export { DashboardSessionStorage } from './dashboard_session_storage'; export { loadSavedDashboardState } from './load_saved_dashboard_state'; export { attemptLoadDashboardByTitle } from './load_dashboard_by_title'; export { syncDashboardFilterState } from './sync_dashboard_filter_state'; -export { syncDashboardIndexPatterns } from './sync_dashboard_index_patterns'; +export { syncDashboardDataViews } from './sync_dashboard_data_views'; export { syncDashboardContainerInput } from './sync_dashboard_container_input'; export { loadDashboardHistoryLocationState } from './load_dashboard_history_location_state'; export { buildDashboardContainer, tryDestroyDashboardContainer } from './build_dashboard_container'; diff --git a/src/plugins/dashboard/public/application/lib/load_saved_dashboard_state.ts b/src/plugins/dashboard/public/application/lib/load_saved_dashboard_state.ts index 03a03842c0e66..45eda98dcc498 100644 --- a/src/plugins/dashboard/public/application/lib/load_saved_dashboard_state.ts +++ b/src/plugins/dashboard/public/application/lib/load_saved_dashboard_state.ts @@ -28,7 +28,7 @@ export const loadSavedDashboardState = async ({ query, history, notifications, - indexPatterns, + dataViews, savedDashboards, usageCollection, savedDashboardId, @@ -51,7 +51,7 @@ export const loadSavedDashboardState = async ({ notifications.toasts.addWarning(getDashboard60Warning()); return; } - await indexPatterns.ensureDefaultDataView(); + await dataViews.ensureDefaultDataView(); try { const savedDashboard = (await savedDashboards.get({ id: savedDashboardId, diff --git a/src/plugins/dashboard/public/application/lib/save_dashboard.ts b/src/plugins/dashboard/public/application/lib/save_dashboard.ts index 5a699eb116401..0be2211d4c2fc 100644 --- a/src/plugins/dashboard/public/application/lib/save_dashboard.ts +++ b/src/plugins/dashboard/public/application/lib/save_dashboard.ts @@ -8,6 +8,7 @@ import _ from 'lodash'; +import { isFilterPinned } from '@kbn/es-query'; import { convertTimeToUTCString } from '.'; import { NotificationsStart } from '../../services/core'; import { DashboardSavedObject } from '../../saved_dashboards'; @@ -16,7 +17,7 @@ import { SavedObjectSaveOpts } from '../../services/saved_objects'; import { dashboardSaveToastStrings } from '../../dashboard_strings'; import { getHasTaggingCapabilitiesGuard } from './dashboard_tagging'; import { SavedObjectsTaggingApi } from '../../services/saved_objects_tagging_oss'; -import { RefreshInterval, TimefilterContract, esFilters } from '../../services/data'; +import { RefreshInterval, TimefilterContract } from '../../services/data'; import { convertPanelStateToSavedDashboardPanel } from '../../../common/embeddable/embeddable_saved_object_converters'; import { DashboardSessionStorage } from './dashboard_session_storage'; import { serializeControlGroupToDashboardSavedObject } from './dashboard_control_group'; @@ -81,9 +82,7 @@ export const saveDashboard = async ({ savedDashboard.refreshInterval = savedDashboard.timeRestore ? timeRestoreObj : undefined; // only save unpinned filters - const unpinnedFilters = savedDashboard - .getFilters() - .filter((filter) => !esFilters.isFilterPinned(filter)); + const unpinnedFilters = savedDashboard.getFilters().filter((filter) => !isFilterPinned(filter)); savedDashboard.searchSource.setField('filter', unpinnedFilters); try { diff --git a/src/plugins/dashboard/public/application/lib/sync_dashboard_container_input.ts b/src/plugins/dashboard/public/application/lib/sync_dashboard_container_input.ts index 0fa7487390cd8..d3930cb5c0621 100644 --- a/src/plugins/dashboard/public/application/lib/sync_dashboard_container_input.ts +++ b/src/plugins/dashboard/public/application/lib/sync_dashboard_container_input.ts @@ -10,8 +10,9 @@ import _ from 'lodash'; import { Subscription } from 'rxjs'; import { debounceTime, tap } from 'rxjs/operators'; +import { compareFilters, COMPARE_ALL_OPTIONS, type Filter } from '@kbn/es-query'; import { DashboardContainer } from '../embeddable'; -import { esFilters, Filter, Query } from '../../services/data'; +import { Query } from '../../services/data'; import { DashboardConstants, DashboardSavedObject } from '../..'; import { setControlGroupState, @@ -96,13 +97,7 @@ export const applyContainerChangesToState = ({ return; } const { filterManager } = query; - if ( - !esFilters.compareFilters( - input.filters, - filterManager.getFilters(), - esFilters.COMPARE_ALL_OPTIONS - ) - ) { + if (!compareFilters(input.filters, filterManager.getFilters(), COMPARE_ALL_OPTIONS)) { // Add filters modifies the object passed to it, hence the clone deep. filterManager.addFilters(_.cloneDeep(input.filters)); applyFilters(latestState.query, input.filters); diff --git a/src/plugins/dashboard/public/application/lib/sync_dashboard_index_patterns.ts b/src/plugins/dashboard/public/application/lib/sync_dashboard_data_views.ts similarity index 56% rename from src/plugins/dashboard/public/application/lib/sync_dashboard_index_patterns.ts rename to src/plugins/dashboard/public/application/lib/sync_dashboard_data_views.ts index 5460ef7b00037..63cecaa76fb2f 100644 --- a/src/plugins/dashboard/public/application/lib/sync_dashboard_index_patterns.ts +++ b/src/plugins/dashboard/public/application/lib/sync_dashboard_data_views.ts @@ -13,48 +13,51 @@ import { distinctUntilChanged, switchMap, filter, mapTo, map } from 'rxjs/operat import { DashboardContainer } from '..'; import { isErrorEmbeddable } from '../../services/embeddable'; -import { IndexPattern, IndexPatternsContract } from '../../services/data'; +import { DataViewsContract } from '../../services/data'; +import { DataView } from '../../services/data_views'; -interface SyncDashboardIndexPatternsProps { +interface SyncDashboardDataViewsProps { dashboardContainer: DashboardContainer; - indexPatterns: IndexPatternsContract; - onUpdateIndexPatterns: (newIndexPatterns: IndexPattern[]) => void; + dataViews: DataViewsContract; + onUpdateDataViews: (newDataViews: DataView[]) => void; } -export const syncDashboardIndexPatterns = ({ +export const syncDashboardDataViews = ({ dashboardContainer, - indexPatterns, - onUpdateIndexPatterns, -}: SyncDashboardIndexPatternsProps) => { - const updateIndexPatternsOperator = pipe( + dataViews, + onUpdateDataViews, +}: SyncDashboardDataViewsProps) => { + const updateDataViewsOperator = pipe( filter((container: DashboardContainer) => !!container && !isErrorEmbeddable(container)), - map((container: DashboardContainer): IndexPattern[] | undefined => { - let panelIndexPatterns: IndexPattern[] = []; + map((container: DashboardContainer): DataView[] | undefined => { + let panelDataViews: DataView[] = []; Object.values(container.getChildIds()).forEach((id) => { const embeddableInstance = container.getChild(id); if (isErrorEmbeddable(embeddableInstance)) return; - const embeddableIndexPatterns = (embeddableInstance.getOutput() as any).indexPatterns; - if (!embeddableIndexPatterns) return; - panelIndexPatterns.push(...embeddableIndexPatterns); + const embeddableDataViews = ( + embeddableInstance.getOutput() as { indexPatterns: DataView[] } + ).indexPatterns; + if (!embeddableDataViews) return; + panelDataViews.push(...embeddableDataViews); }); if (container.controlGroup) { - panelIndexPatterns.push(...(container.controlGroup.getOutput().dataViews ?? [])); + panelDataViews.push(...(container.controlGroup.getOutput().dataViews ?? [])); } - panelIndexPatterns = uniqBy(panelIndexPatterns, 'id'); + panelDataViews = uniqBy(panelDataViews, 'id'); /** * If no index patterns have been returned yet, and there is at least one embeddable which * hasn't yet loaded, defer the loading of the default index pattern by returning undefined. */ if ( - panelIndexPatterns.length === 0 && + panelDataViews.length === 0 && Object.keys(container.getOutput().embeddableLoaded).length > 0 && Object.values(container.getOutput().embeddableLoaded).some((value) => value === false) ) { return; } - return panelIndexPatterns; + return panelDataViews; }), distinctUntilChanged((a, b) => deepEqual( @@ -63,17 +66,17 @@ export const syncDashboardIndexPatterns = ({ ) ), // using switchMap for previous task cancellation - switchMap((panelIndexPatterns?: IndexPattern[]) => { + switchMap((panelDataViews?: DataView[]) => { return new Observable((observer) => { - if (!panelIndexPatterns) return; - if (panelIndexPatterns.length > 0) { + if (!panelDataViews) return; + if (panelDataViews.length > 0) { if (observer.closed) return; - onUpdateIndexPatterns(panelIndexPatterns); + onUpdateDataViews(panelDataViews); observer.complete(); } else { - indexPatterns.getDefault().then((defaultIndexPattern) => { + dataViews.getDefault().then((defaultDataView) => { if (observer.closed) return; - onUpdateIndexPatterns([defaultIndexPattern as IndexPattern]); + onUpdateDataViews([defaultDataView as DataView]); observer.complete(); }); } @@ -81,11 +84,11 @@ export const syncDashboardIndexPatterns = ({ }) ); - const indexPatternSources = [dashboardContainer.getOutput$()]; + const dataViewSources = [dashboardContainer.getOutput$()]; if (dashboardContainer.controlGroup) - indexPatternSources.push(dashboardContainer.controlGroup.getOutput$()); + dataViewSources.push(dashboardContainer.controlGroup.getOutput$()); - return combineLatest(indexPatternSources) - .pipe(mapTo(dashboardContainer), updateIndexPatternsOperator) + return combineLatest(dataViewSources) + .pipe(mapTo(dashboardContainer), updateDataViewsOperator) .subscribe(); }; diff --git a/src/plugins/dashboard/public/application/lib/sync_dashboard_url_state.ts b/src/plugins/dashboard/public/application/lib/sync_dashboard_url_state.ts index e0a1526baa473..392b37bb4d8e0 100644 --- a/src/plugins/dashboard/public/application/lib/sync_dashboard_url_state.ts +++ b/src/plugins/dashboard/public/application/lib/sync_dashboard_url_state.ts @@ -94,13 +94,13 @@ const loadDashboardUrlState = ({ if (!awaitingRemoval) { awaitingRemoval = true; kbnUrlStateStorage.kbnUrlControls.updateAsync((nextUrl) => { + awaitingRemoval = false; if (nextUrl.includes(DASHBOARD_STATE_STORAGE_KEY)) { return replaceUrlHashQuery(nextUrl, (query) => { delete query[DASHBOARD_STATE_STORAGE_KEY]; return query; }); } - awaitingRemoval = false; return nextUrl; }, true); } diff --git a/src/plugins/dashboard/public/application/listing/get_dashboard_list_item_link.test.ts b/src/plugins/dashboard/public/application/listing/get_dashboard_list_item_link.test.ts index 36b8b57cfdbd8..ce9535e549446 100644 --- a/src/plugins/dashboard/public/application/listing/get_dashboard_list_item_link.test.ts +++ b/src/plugins/dashboard/public/application/listing/get_dashboard_list_item_link.test.ts @@ -8,10 +8,10 @@ import { getDashboardListItemLink } from './get_dashboard_list_item_link'; import { ApplicationStart } from 'kibana/public'; -import { esFilters } from '../../../../data/public'; import { createHashHistory } from 'history'; +import { FilterStateStore } from '@kbn/es-query'; import { createKbnUrlStateStorage } from '../../../../kibana_utils/public'; -import { GLOBAL_STATE_STORAGE_KEY } from '../../url_generator'; +import { GLOBAL_STATE_STORAGE_KEY } from '../../dashboard_constants'; const DASHBOARD_ID = '13823000-99b9-11ea-9eb6-d9e8adceb647'; @@ -118,7 +118,7 @@ describe('when global filters change', () => { }, query: { query: 'q1' }, $state: { - store: esFilters.FilterStateStore.GLOBAL_STATE, + store: FilterStateStore.GLOBAL_STATE, }, }, ]; diff --git a/src/plugins/dashboard/public/application/listing/get_dashboard_list_item_link.ts b/src/plugins/dashboard/public/application/listing/get_dashboard_list_item_link.ts index 2f19924d45982..8af3f2a10666f 100644 --- a/src/plugins/dashboard/public/application/listing/get_dashboard_list_item_link.ts +++ b/src/plugins/dashboard/public/application/listing/get_dashboard_list_item_link.ts @@ -9,8 +9,11 @@ import { ApplicationStart } from 'kibana/public'; import { QueryState } from '../../../../data/public'; import { setStateToKbnUrl } from '../../../../kibana_utils/public'; -import { createDashboardEditUrl, DashboardConstants } from '../../dashboard_constants'; -import { GLOBAL_STATE_STORAGE_KEY } from '../../url_generator'; +import { + DashboardConstants, + createDashboardEditUrl, + GLOBAL_STATE_STORAGE_KEY, +} from '../../dashboard_constants'; import { IKbnUrlStateStorage } from '../../services/kibana_utils'; export const getDashboardListItemLink = ( diff --git a/src/plugins/dashboard/public/application/test_helpers/make_default_services.ts b/src/plugins/dashboard/public/application/test_helpers/make_default_services.ts index 616fe56102df9..656f5672e38c0 100644 --- a/src/plugins/dashboard/public/application/test_helpers/make_default_services.ts +++ b/src/plugins/dashboard/public/application/test_helpers/make_default_services.ts @@ -13,7 +13,7 @@ import { UrlForwardingStart } from '../../../../url_forwarding/public'; import { NavigationPublicPluginStart } from '../../services/navigation'; import { DashboardAppServices, DashboardAppCapabilities } from '../../types'; import { embeddablePluginMock } from '../../../../embeddable/public/mocks'; -import { IndexPatternsContract, SavedQueryService } from '../../services/data'; +import { DataViewsContract, SavedQueryService } from '../../services/data'; import { savedObjectsPluginMock } from '../../../../saved_objects/public/mocks'; import { screenshotModePluginMock } from '../../../../screenshot_mode/public/mocks'; import { visualizationsPluginMock } from '../../../../visualizations/public/mocks'; @@ -83,7 +83,7 @@ export function makeDefaultServices(): DashboardAppServices { savedObjectsClient: core.savedObjects.client, dashboardCapabilities: defaultCapabilities, data: dataPluginMock.createStartContract(), - indexPatterns: {} as IndexPatternsContract, + dataViews: {} as DataViewsContract, savedQueryService: {} as SavedQueryService, scopedHistory: () => ({} as ScopedHistory), setHeaderActionMenu: (mountPoint) => {}, diff --git a/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx b/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx index 005d40a90f38f..eb251ad41f62b 100644 --- a/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx +++ b/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx @@ -525,7 +525,7 @@ export function DashboardTopNav({ showDatePicker, showFilterBar, setMenuMountPoint: embedSettings ? undefined : setHeaderActionMenu, - indexPatterns: dashboardAppState.indexPatterns, + indexPatterns: dashboardAppState.dataViews, showSaveQuery: dashboardCapabilities.saveQuery, useDefaultBehaviors: true, savedQuery: state.savedQuery, diff --git a/src/plugins/dashboard/public/dashboard_constants.ts b/src/plugins/dashboard/public/dashboard_constants.ts index 9063b279c25f2..88fbc3b30392f 100644 --- a/src/plugins/dashboard/public/dashboard_constants.ts +++ b/src/plugins/dashboard/public/dashboard_constants.ts @@ -9,6 +9,7 @@ import type { ControlStyle } from '../../controls/public'; export const DASHBOARD_STATE_STORAGE_KEY = '_a'; +export const GLOBAL_STATE_STORAGE_KEY = '_g'; export const DashboardConstants = { LANDING_PAGE_PATH: '/list', diff --git a/src/plugins/dashboard/public/index.ts b/src/plugins/dashboard/public/index.ts index f25a92275d723..bff2d4d79108c 100644 --- a/src/plugins/dashboard/public/index.ts +++ b/src/plugins/dashboard/public/index.ts @@ -16,15 +16,7 @@ export { } from './application'; export { DashboardConstants, createDashboardEditUrl } from './dashboard_constants'; -export type { - DashboardSetup, - DashboardStart, - DashboardUrlGenerator, - DashboardFeatureFlagConfig, -} from './plugin'; - -export type { DashboardUrlGeneratorState } from './url_generator'; -export { DASHBOARD_APP_URL_GENERATOR, createDashboardUrlGenerator } from './url_generator'; +export type { DashboardSetup, DashboardStart, DashboardFeatureFlagConfig } from './plugin'; export type { DashboardAppLocator, DashboardAppLocatorParams } from './locator'; export type { DashboardSavedObject } from './saved_dashboards'; diff --git a/src/plugins/dashboard/public/locator.test.ts b/src/plugins/dashboard/public/locator.test.ts index f3f5aec9f478c..11ec16908b811 100644 --- a/src/plugins/dashboard/public/locator.test.ts +++ b/src/plugins/dashboard/public/locator.test.ts @@ -9,7 +9,7 @@ import { DashboardAppLocatorDefinition } from './locator'; import { hashedItemStore } from '../../kibana_utils/public'; import { mockStorage } from '../../kibana_utils/public/storage/hashed_item_store/mock'; -import { esFilters } from '../../data/public'; +import { FilterStateStore } from '@kbn/es-query'; describe('dashboard locator', () => { beforeEach(() => { @@ -79,7 +79,7 @@ describe('dashboard locator', () => { }, query: { query: 'hi' }, $state: { - store: esFilters.FilterStateStore.GLOBAL_STATE, + store: FilterStateStore.GLOBAL_STATE, }, }, ], diff --git a/src/plugins/dashboard/public/locator.ts b/src/plugins/dashboard/public/locator.ts index b6655e246de36..42efb521cf6e5 100644 --- a/src/plugins/dashboard/public/locator.ts +++ b/src/plugins/dashboard/public/locator.ts @@ -8,11 +8,11 @@ import type { SerializableRecord } from '@kbn/utility-types'; import { flow } from 'lodash'; -import type { TimeRange, Filter, Query, QueryState, RefreshInterval } from '../../data/public'; +import { type Filter } from '@kbn/es-query'; +import type { TimeRange, Query, QueryState, RefreshInterval } from '../../data/public'; import type { LocatorDefinition, LocatorPublic } from '../../share/public'; import type { SavedDashboardPanel } from '../common/types'; import type { RawDashboardState } from './types'; -import { esFilters } from '../../data/public'; import { setStateToKbnUrl } from '../../kibana_utils/public'; import { ViewMode } from '../../embeddable/public'; import { DashboardConstants } from './dashboard_constants'; @@ -152,12 +152,14 @@ export class DashboardAppLocatorDefinition implements LocatorDefinition( '_g', cleanEmptyKeys({ time: params.timeRange, - filters: filters?.filter((f) => esFilters.isFilterPinned(f)), + filters: filters?.filter((f) => isFilterPinned(f)), refreshInterval: params.refreshInterval, }), { useHash }, diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index 7f784d43c0cb7..2f63062ccf60c 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -33,8 +33,8 @@ import { UiActionsSetup, UiActionsStart } from './services/ui_actions'; import { PresentationUtilPluginStart } from './services/presentation_util'; import { FeatureCatalogueCategory, HomePublicPluginSetup } from './services/home'; import { NavigationPublicPluginStart as NavigationStart } from './services/navigation'; -import { DataPublicPluginSetup, DataPublicPluginStart, esFilters } from './services/data'; -import { SharePluginSetup, SharePluginStart, UrlGeneratorContract } from './services/share'; +import { DataPublicPluginSetup, DataPublicPluginStart } from './services/data'; +import { SharePluginSetup, SharePluginStart } from './services/share'; import type { SavedObjectTaggingOssPluginStart } from './services/saved_objects_tagging_oss'; import type { ScreenshotModePluginSetup, @@ -70,29 +70,15 @@ import { CopyToDashboardAction, DashboardCapabilities, } from './application'; -import { - createDashboardUrlGenerator, - DASHBOARD_APP_URL_GENERATOR, - DashboardUrlGeneratorState, -} from './url_generator'; import { DashboardAppLocatorDefinition, DashboardAppLocator } from './locator'; import { createSavedDashboardLoader } from './saved_dashboards'; import { DashboardConstants } from './dashboard_constants'; import { PlaceholderEmbeddableFactory } from './application/embeddable/placeholder'; -import { UrlGeneratorState } from '../../share/public'; import { ExportCSVAction } from './application/actions/export_csv_action'; import { dashboardFeatureCatalog } from './dashboard_strings'; import { replaceUrlHashQuery } from '../../kibana_utils/public'; import { SpacesPluginStart } from './services/spaces'; -declare module '../../share/public' { - export interface UrlGeneratorStateMapping { - [DASHBOARD_APP_URL_GENERATOR]: UrlGeneratorState; - } -} - -export type DashboardUrlGenerator = UrlGeneratorContract; - export interface DashboardFeatureFlagConfig { allowByValueEmbeddables: boolean; } @@ -134,15 +120,6 @@ export interface DashboardStart { getDashboardContainerByValueRenderer: () => ReturnType< typeof createDashboardContainerByValueRenderer >; - /** - * @deprecated Use dashboard locator instead. Dashboard locator is available - * under `.locator` key. This dashboard URL generator will be removed soon. - * - * ```ts - * plugins.dashboard.locator.getLocation({ ... }); - * ``` - */ - dashboardUrlGenerator?: DashboardUrlGenerator; locator?: DashboardAppLocator; dashboardFeatureFlagConfig: DashboardFeatureFlagConfig; } @@ -157,11 +134,6 @@ export class DashboardPlugin private stopUrlTracking: (() => void) | undefined = undefined; private currentHistory: ScopedHistory | undefined = undefined; private dashboardFeatureFlagConfig?: DashboardFeatureFlagConfig; - - /** - * @deprecated Use locator instead. - */ - private dashboardUrlGenerator?: DashboardUrlGenerator; private locator?: DashboardAppLocator; public setup( @@ -178,20 +150,6 @@ export class DashboardPlugin ): DashboardSetup { this.dashboardFeatureFlagConfig = this.initializerContext.config.get(); - const startServices = core.getStartServices(); - - if (share) { - this.dashboardUrlGenerator = share.urlGenerators.registerUrlGenerator( - createDashboardUrlGenerator(async () => { - const [coreStart, , selfStart] = await startServices; - return { - appBasePath: coreStart.application.getUrlForApp('dashboards'), - useHashedUrl: coreStart.uiSettings.get('state:storeInSessionStorage'), - savedDashboardLoader: selfStart.getSavedDashboardLoader(), - }; - }) - ); - } const getPlaceholderEmbeddableStartServices = async () => { const [coreStart] = await core.getStartServices(); @@ -253,10 +211,13 @@ export class DashboardPlugin filter( ({ changes }) => !!(changes.globalFilters || changes.time || changes.refreshInterval) ), - map(({ state }) => ({ - ...state, - filters: state.filters?.filter(esFilters.isFilterPinned), - })) + map(async ({ state }) => { + const { isFilterPinned } = await import('@kbn/es-query'); + return { + ...state, + filters: state.filters?.filter(isFilterPinned), + }; + }) ), }, ], @@ -455,7 +416,6 @@ export class DashboardPlugin factory: dashboardContainerFactory as DashboardContainerFactory, }); }, - dashboardUrlGenerator: this.dashboardUrlGenerator, locator: this.locator, dashboardFeatureFlagConfig: this.dashboardFeatureFlagConfig!, }; diff --git a/src/plugins/dashboard/public/services/data_views.ts b/src/plugins/dashboard/public/services/data_views.ts new file mode 100644 index 0000000000000..4fb2bbaf08503 --- /dev/null +++ b/src/plugins/dashboard/public/services/data_views.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from '../../../data_views/public'; diff --git a/src/plugins/saved_objects/public/saved_object/saved_object_loader.ts b/src/plugins/dashboard/public/services/saved_object_loader.ts similarity index 95% rename from src/plugins/saved_objects/public/saved_object/saved_object_loader.ts rename to src/plugins/dashboard/public/services/saved_object_loader.ts index 10872b5d9cd1a..521d6645e30f9 100644 --- a/src/plugins/saved_objects/public/saved_object/saved_object_loader.ts +++ b/src/plugins/dashboard/public/services/saved_object_loader.ts @@ -12,9 +12,13 @@ import { SavedObjectsFindOptionsReference, SavedObjectReference, } from 'kibana/public'; -import { SavedObject } from '../types'; -import { StringUtils } from './helpers/string_utils'; +import { SavedObject } from '../../../saved_objects/public'; +import { upperFirst } from './string_utils'; +/** + * @deprecated + * @removeBy 8.0 + */ export interface SavedObjectLoaderFindOptions { size?: number; fields?: string[]; @@ -23,6 +27,8 @@ export interface SavedObjectLoaderFindOptions { /** * @deprecated + * @removeBy 8.0 + * * The SavedObjectLoader class provides some convenience functions * to load and save one kind of saved objects (specified in the constructor). * @@ -46,7 +52,7 @@ export class SavedObjectLoader { this.loaderProperties = { name: `${this.lowercaseType}s`, - noun: StringUtils.upperFirst(this.type), + noun: upperFirst(this.type), nouns: `${this.lowercaseType}s`, }; } diff --git a/src/plugins/dashboard/public/services/saved_objects.ts b/src/plugins/dashboard/public/services/saved_objects.ts index 305ff3c2014f8..afd778d78b271 100644 --- a/src/plugins/dashboard/public/services/saved_objects.ts +++ b/src/plugins/dashboard/public/services/saved_objects.ts @@ -11,11 +11,11 @@ export type { SavedObject, SavedObjectsStart, SavedObjectSaveOpts, - SavedObjectLoaderFindOptions, } from '../../../saved_objects/public'; export { showSaveModal, - SavedObjectLoader, SavedObjectSaveModal, getSavedObjectFinder, } from '../../../saved_objects/public'; +export { SavedObjectLoader } from './saved_object_loader'; +export type { SavedObjectLoaderFindOptions } from './saved_object_loader'; diff --git a/src/plugins/dashboard/public/services/screenshot_mode.ts b/src/plugins/dashboard/public/services/screenshot_mode.ts index 12ec1bca2207f..577aa3eab36b5 100644 --- a/src/plugins/dashboard/public/services/screenshot_mode.ts +++ b/src/plugins/dashboard/public/services/screenshot_mode.ts @@ -10,5 +10,3 @@ export type { ScreenshotModePluginStart, ScreenshotModePluginSetup, } from '../../../screenshot_mode/public'; - -export type { Layout } from '../../../screenshot_mode/common'; diff --git a/src/plugins/dashboard/public/services/share.ts b/src/plugins/dashboard/public/services/share.ts index 7ed9b86571596..77a9f44a3cf00 100644 --- a/src/plugins/dashboard/public/services/share.ts +++ b/src/plugins/dashboard/public/services/share.ts @@ -6,9 +6,5 @@ * Side Public License, v 1. */ -export type { - SharePluginStart, - SharePluginSetup, - UrlGeneratorContract, -} from '../../../share/public'; +export type { SharePluginStart, SharePluginSetup } from '../../../share/public'; export { downloadMultipleAs } from '../../../share/public'; diff --git a/src/plugins/saved_objects/public/saved_object/helpers/string_utils.test.ts b/src/plugins/dashboard/public/services/string_utils.test.ts similarity index 53% rename from src/plugins/saved_objects/public/saved_object/helpers/string_utils.test.ts rename to src/plugins/dashboard/public/services/string_utils.test.ts index 4e7258f1575dc..ed96cb4f1a0a1 100644 --- a/src/plugins/saved_objects/public/saved_object/helpers/string_utils.test.ts +++ b/src/plugins/dashboard/public/services/string_utils.test.ts @@ -6,17 +6,17 @@ * Side Public License, v 1. */ -import { StringUtils } from './string_utils'; +import { upperFirst } from './string_utils'; -describe('StringUtils class', () => { - describe('static upperFirst', () => { +describe('StringUtils', () => { + describe('upperFirst', () => { test('should converts the first character of string to upper case', () => { - expect(StringUtils.upperFirst()).toBe(''); - expect(StringUtils.upperFirst('')).toBe(''); + expect(upperFirst()).toBe(''); + expect(upperFirst('')).toBe(''); - expect(StringUtils.upperFirst('Fred')).toBe('Fred'); - expect(StringUtils.upperFirst('fred')).toBe('Fred'); - expect(StringUtils.upperFirst('FRED')).toBe('FRED'); + expect(upperFirst('Fred')).toBe('Fred'); + expect(upperFirst('fred')).toBe('Fred'); + expect(upperFirst('FRED')).toBe('FRED'); }); }); }); diff --git a/src/plugins/saved_objects/public/saved_object/helpers/string_utils.ts b/src/plugins/dashboard/public/services/string_utils.ts similarity index 55% rename from src/plugins/saved_objects/public/saved_object/helpers/string_utils.ts rename to src/plugins/dashboard/public/services/string_utils.ts index 5d4551f0c200c..31a36b38155d7 100644 --- a/src/plugins/saved_objects/public/saved_object/helpers/string_utils.ts +++ b/src/plugins/dashboard/public/services/string_utils.ts @@ -6,13 +6,11 @@ * Side Public License, v 1. */ -export class StringUtils { - /** - * Returns a version of the string with the first letter capitalized. - * @param str {string} - * @returns {string} - */ - public static upperFirst(str: string = ''): string { - return str ? str.charAt(0).toUpperCase() + str.slice(1) : ''; - } +/** + * Returns a version of the string with the first letter capitalized. + * @param str {string} + * @returns {string} + */ +export function upperFirst(str: string = ''): string { + return str ? str.charAt(0).toUpperCase() + str.slice(1) : ''; } diff --git a/src/plugins/dashboard/public/types.ts b/src/plugins/dashboard/public/types.ts index b7b146aeba348..4de07974203a7 100644 --- a/src/plugins/dashboard/public/types.ts +++ b/src/plugins/dashboard/public/types.ts @@ -17,22 +17,25 @@ import type { KibanaExecutionContext, } from 'kibana/public'; import { History } from 'history'; +import type { Filter } from '@kbn/es-query'; import { AnyAction, Dispatch } from 'redux'; import { BehaviorSubject, Subject } from 'rxjs'; -import { Query, Filter, IndexPattern, RefreshInterval, TimeRange } from './services/data'; -import { ContainerInput, EmbeddableInput, ViewMode } from './services/embeddable'; + +import { DataView } from './services/data_views'; import { SharePluginStart } from './services/share'; import { EmbeddableStart } from './services/embeddable'; import { DashboardSessionStorage } from './application/lib'; import { UrlForwardingStart } from '../../url_forwarding/public'; import { UsageCollectionSetup } from './services/usage_collection'; import { NavigationPublicPluginStart } from './services/navigation'; +import { Query, RefreshInterval, TimeRange } from './services/data'; import { DashboardPanelState, SavedDashboardPanel } from '../common/types'; import { SavedObjectsTaggingApi } from './services/saved_objects_tagging_oss'; -import { DataPublicPluginStart, IndexPatternsContract } from './services/data'; +import { DataPublicPluginStart, DataViewsContract } from './services/data'; +import { ContainerInput, EmbeddableInput, ViewMode } from './services/embeddable'; import { SavedObjectLoader, SavedObjectsStart } from './services/saved_objects'; -import { IKbnUrlStateStorage } from './services/kibana_utils'; import type { ScreenshotModePluginStart } from './services/screenshot_mode'; +import { IKbnUrlStateStorage } from './services/kibana_utils'; import type { DashboardContainer, DashboardSavedObject } from '.'; import { VisualizationsStart } from '../../visualizations/public'; import { DashboardAppLocatorParams } from './locator'; @@ -102,7 +105,7 @@ export interface DashboardContainerInput extends ContainerInput { */ export interface DashboardAppState { hasUnsavedChanges?: boolean; - indexPatterns?: IndexPattern[]; + dataViews?: DataView[]; updateLastSavedState?: () => void; resetToLastSavedState?: () => void; savedDashboard?: DashboardSavedObject; @@ -119,7 +122,7 @@ export interface DashboardAppState { export type DashboardBuildContext = Pick< DashboardAppServices, | 'embeddable' - | 'indexPatterns' + | 'dataViews' | 'savedDashboards' | 'usageCollection' | 'initializerContext' @@ -198,7 +201,7 @@ export interface DashboardAppServices { savedDashboards: SavedObjectLoader; scopedHistory: () => ScopedHistory; visualizations: VisualizationsStart; - indexPatterns: IndexPatternsContract; + dataViews: DataViewsContract; usageCollection?: UsageCollectionSetup; navigation: NavigationPublicPluginStart; dashboardCapabilities: DashboardAppCapabilities; diff --git a/src/plugins/dashboard/public/url_generator.test.ts b/src/plugins/dashboard/public/url_generator.test.ts deleted file mode 100644 index 9a1204f116c7f..0000000000000 --- a/src/plugins/dashboard/public/url_generator.test.ts +++ /dev/null @@ -1,356 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { createDashboardUrlGenerator } from './url_generator'; -import { hashedItemStore } from '../../kibana_utils/public'; -import { mockStorage } from '../../kibana_utils/public/storage/hashed_item_store/mock'; -import { esFilters, Filter } from '../../data/public'; -import { SavedObjectLoader } from '../../saved_objects/public'; - -const APP_BASE_PATH: string = 'xyz/app/dashboards'; - -const createMockDashboardLoader = ( - dashboardToFilters: { - [dashboardId: string]: () => Filter[]; - } = {} -) => { - return { - get: async (dashboardId: string) => { - return { - searchSource: { - getField: (field: string) => { - if (field === 'filter') - return dashboardToFilters[dashboardId] ? dashboardToFilters[dashboardId]() : []; - throw new Error( - `createMockDashboardLoader > searchSource > getField > ${field} is not mocked` - ); - }, - }, - }; - }, - } as SavedObjectLoader; -}; - -describe('dashboard url generator', () => { - beforeEach(() => { - // @ts-ignore - hashedItemStore.storage = mockStorage; - }); - - test('creates a link to a saved dashboard', async () => { - const generator = createDashboardUrlGenerator(() => - Promise.resolve({ - appBasePath: APP_BASE_PATH, - useHashedUrl: false, - savedDashboardLoader: createMockDashboardLoader(), - }) - ); - const url = await generator.createUrl!({}); - expect(url).toMatchInlineSnapshot(`"xyz/app/dashboards#/create?_a=()&_g=()"`); - }); - - test('creates a link with global time range set up', async () => { - const generator = createDashboardUrlGenerator(() => - Promise.resolve({ - appBasePath: APP_BASE_PATH, - useHashedUrl: false, - savedDashboardLoader: createMockDashboardLoader(), - }) - ); - const url = await generator.createUrl!({ - timeRange: { to: 'now', from: 'now-15m', mode: 'relative' }, - }); - expect(url).toMatchInlineSnapshot( - `"xyz/app/dashboards#/create?_a=()&_g=(time:(from:now-15m,mode:relative,to:now))"` - ); - }); - - test('creates a link with filters, time range, refresh interval and query to a saved object', async () => { - const generator = createDashboardUrlGenerator(() => - Promise.resolve({ - appBasePath: APP_BASE_PATH, - useHashedUrl: false, - savedDashboardLoader: createMockDashboardLoader(), - }) - ); - const url = await generator.createUrl!({ - timeRange: { to: 'now', from: 'now-15m', mode: 'relative' }, - refreshInterval: { pause: false, value: 300 }, - dashboardId: '123', - filters: [ - { - meta: { - alias: null, - disabled: false, - negate: false, - }, - query: { query: 'hi' }, - }, - { - meta: { - alias: null, - disabled: false, - negate: false, - }, - query: { query: 'hi' }, - $state: { - store: esFilters.FilterStateStore.GLOBAL_STATE, - }, - }, - ], - query: { query: 'bye', language: 'kuery' }, - }); - expect(url).toMatchInlineSnapshot( - `"xyz/app/dashboards#/view/123?_a=(filters:!((meta:(alias:!n,disabled:!f,negate:!f),query:(query:hi))),query:(language:kuery,query:bye))&_g=(filters:!(('$state':(store:globalState),meta:(alias:!n,disabled:!f,negate:!f),query:(query:hi))),refreshInterval:(pause:!f,value:300),time:(from:now-15m,mode:relative,to:now))"` - ); - }); - - test('searchSessionId', async () => { - const generator = createDashboardUrlGenerator(() => - Promise.resolve({ - appBasePath: APP_BASE_PATH, - useHashedUrl: false, - savedDashboardLoader: createMockDashboardLoader(), - }) - ); - const url = await generator.createUrl!({ - timeRange: { to: 'now', from: 'now-15m', mode: 'relative' }, - refreshInterval: { pause: false, value: 300 }, - dashboardId: '123', - filters: [], - query: { query: 'bye', language: 'kuery' }, - searchSessionId: '__sessionSearchId__', - }); - expect(url).toMatchInlineSnapshot( - `"xyz/app/dashboards#/view/123?_a=(filters:!(),query:(language:kuery,query:bye))&_g=(filters:!(),refreshInterval:(pause:!f,value:300),time:(from:now-15m,mode:relative,to:now))&searchSessionId=__sessionSearchId__"` - ); - }); - - test('savedQuery', async () => { - const generator = createDashboardUrlGenerator(() => - Promise.resolve({ - appBasePath: APP_BASE_PATH, - useHashedUrl: false, - savedDashboardLoader: createMockDashboardLoader(), - }) - ); - const url = await generator.createUrl!({ - savedQuery: '__savedQueryId__', - }); - expect(url).toMatchInlineSnapshot( - `"xyz/app/dashboards#/create?_a=(savedQuery:__savedQueryId__)&_g=()"` - ); - expect(url).toContain('__savedQueryId__'); - }); - - test('panels', async () => { - const generator = createDashboardUrlGenerator(() => - Promise.resolve({ - appBasePath: APP_BASE_PATH, - useHashedUrl: false, - savedDashboardLoader: createMockDashboardLoader(), - }) - ); - const url = await generator.createUrl!({ - panels: [{ fakePanelContent: 'fakePanelContent' } as any], - }); - expect(url).toMatchInlineSnapshot( - `"xyz/app/dashboards#/create?_a=(panels:!((fakePanelContent:fakePanelContent)))&_g=()"` - ); - }); - - test('if no useHash setting is given, uses the one was start services', async () => { - const generator = createDashboardUrlGenerator(() => - Promise.resolve({ - appBasePath: APP_BASE_PATH, - useHashedUrl: true, - savedDashboardLoader: createMockDashboardLoader(), - }) - ); - const url = await generator.createUrl!({ - timeRange: { to: 'now', from: 'now-15m', mode: 'relative' }, - }); - expect(url.indexOf('relative')).toBe(-1); - }); - - test('can override a false useHash ui setting', async () => { - const generator = createDashboardUrlGenerator(() => - Promise.resolve({ - appBasePath: APP_BASE_PATH, - useHashedUrl: false, - savedDashboardLoader: createMockDashboardLoader(), - }) - ); - const url = await generator.createUrl!({ - timeRange: { to: 'now', from: 'now-15m', mode: 'relative' }, - useHash: true, - }); - expect(url.indexOf('relative')).toBe(-1); - }); - - test('can override a true useHash ui setting', async () => { - const generator = createDashboardUrlGenerator(() => - Promise.resolve({ - appBasePath: APP_BASE_PATH, - useHashedUrl: true, - savedDashboardLoader: createMockDashboardLoader(), - }) - ); - const url = await generator.createUrl!({ - timeRange: { to: 'now', from: 'now-15m', mode: 'relative' }, - useHash: false, - }); - expect(url.indexOf('relative')).toBeGreaterThan(1); - }); - - describe('preserving saved filters', () => { - const savedFilter1 = { - meta: { - alias: null, - disabled: false, - negate: false, - }, - query: { query: 'savedfilter1' }, - }; - - const savedFilter2 = { - meta: { - alias: null, - disabled: false, - negate: false, - }, - query: { query: 'savedfilter2' }, - }; - - const appliedFilter = { - meta: { - alias: null, - disabled: false, - negate: false, - }, - query: { query: 'appliedfilter' }, - }; - - test('attaches filters from destination dashboard', async () => { - const generator = createDashboardUrlGenerator(() => - Promise.resolve({ - appBasePath: APP_BASE_PATH, - useHashedUrl: false, - savedDashboardLoader: createMockDashboardLoader({ - ['dashboard1']: () => [savedFilter1], - ['dashboard2']: () => [savedFilter2], - }), - }) - ); - - const urlToDashboard1 = await generator.createUrl!({ - dashboardId: 'dashboard1', - filters: [appliedFilter], - }); - - expect(urlToDashboard1).toEqual(expect.stringContaining('query:savedfilter1')); - expect(urlToDashboard1).toEqual(expect.stringContaining('query:appliedfilter')); - - const urlToDashboard2 = await generator.createUrl!({ - dashboardId: 'dashboard2', - filters: [appliedFilter], - }); - - expect(urlToDashboard2).toEqual(expect.stringContaining('query:savedfilter2')); - expect(urlToDashboard2).toEqual(expect.stringContaining('query:appliedfilter')); - }); - - test("doesn't fail if can't retrieve filters from destination dashboard", async () => { - const generator = createDashboardUrlGenerator(() => - Promise.resolve({ - appBasePath: APP_BASE_PATH, - useHashedUrl: false, - savedDashboardLoader: createMockDashboardLoader({ - ['dashboard1']: () => { - throw new Error('Not found'); - }, - }), - }) - ); - - const url = await generator.createUrl!({ - dashboardId: 'dashboard1', - filters: [appliedFilter], - }); - - expect(url).not.toEqual(expect.stringContaining('query:savedfilter1')); - expect(url).toEqual(expect.stringContaining('query:appliedfilter')); - }); - - test('can enforce empty filters', async () => { - const generator = createDashboardUrlGenerator(() => - Promise.resolve({ - appBasePath: APP_BASE_PATH, - useHashedUrl: false, - savedDashboardLoader: createMockDashboardLoader({ - ['dashboard1']: () => [savedFilter1], - }), - }) - ); - - const url = await generator.createUrl!({ - dashboardId: 'dashboard1', - filters: [], - preserveSavedFilters: false, - }); - - expect(url).not.toEqual(expect.stringContaining('query:savedfilter1')); - expect(url).not.toEqual(expect.stringContaining('query:appliedfilter')); - expect(url).toMatchInlineSnapshot( - `"xyz/app/dashboards#/view/dashboard1?_a=(filters:!())&_g=(filters:!())"` - ); - }); - - test('no filters in result url if no filters applied', async () => { - const generator = createDashboardUrlGenerator(() => - Promise.resolve({ - appBasePath: APP_BASE_PATH, - useHashedUrl: false, - savedDashboardLoader: createMockDashboardLoader({ - ['dashboard1']: () => [savedFilter1], - }), - }) - ); - - const url = await generator.createUrl!({ - dashboardId: 'dashboard1', - }); - expect(url).not.toEqual(expect.stringContaining('filters')); - expect(url).toMatchInlineSnapshot(`"xyz/app/dashboards#/view/dashboard1?_a=()&_g=()"`); - }); - - test('can turn off preserving filters', async () => { - const generator = createDashboardUrlGenerator(() => - Promise.resolve({ - appBasePath: APP_BASE_PATH, - useHashedUrl: false, - savedDashboardLoader: createMockDashboardLoader({ - ['dashboard1']: () => [savedFilter1], - }), - }) - ); - const urlWithPreservedFiltersTurnedOff = await generator.createUrl!({ - dashboardId: 'dashboard1', - filters: [appliedFilter], - preserveSavedFilters: false, - }); - - expect(urlWithPreservedFiltersTurnedOff).not.toEqual( - expect.stringContaining('query:savedfilter1') - ); - expect(urlWithPreservedFiltersTurnedOff).toEqual( - expect.stringContaining('query:appliedfilter') - ); - }); - }); -}); diff --git a/src/plugins/dashboard/public/url_generator.ts b/src/plugins/dashboard/public/url_generator.ts deleted file mode 100644 index 5c0cd32ee5a16..0000000000000 --- a/src/plugins/dashboard/public/url_generator.ts +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { - TimeRange, - Filter, - Query, - esFilters, - QueryState, - RefreshInterval, -} from '../../data/public'; -import { setStateToKbnUrl } from '../../kibana_utils/public'; -import { UrlGeneratorsDefinition } from '../../share/public'; -import { SavedObjectLoader } from '../../saved_objects/public'; -import { ViewMode } from '../../embeddable/public'; -import { DashboardConstants } from './dashboard_constants'; -import { SavedDashboardPanel } from '../common/types'; - -export const STATE_STORAGE_KEY = '_a'; -export const GLOBAL_STATE_STORAGE_KEY = '_g'; - -export const DASHBOARD_APP_URL_GENERATOR = 'DASHBOARD_APP_URL_GENERATOR'; - -/** - * @deprecated Use dashboard locator instead. - */ -export interface DashboardUrlGeneratorState { - /** - * If given, the dashboard saved object with this id will be loaded. If not given, - * a new, unsaved dashboard will be loaded up. - */ - dashboardId?: string; - /** - * Optionally set the time range in the time picker. - */ - timeRange?: TimeRange; - - /** - * Optionally set the refresh interval. - */ - refreshInterval?: RefreshInterval; - - /** - * Optionally apply filers. NOTE: if given and used in conjunction with `dashboardId`, and the - * saved dashboard has filters saved with it, this will _replace_ those filters. - */ - filters?: Filter[]; - /** - * Optionally set a query. NOTE: if given and used in conjunction with `dashboardId`, and the - * saved dashboard has a query saved with it, this will _replace_ that query. - */ - query?: Query; - /** - * If not given, will use the uiSettings configuration for `storeInSessionStorage`. useHash determines - * whether to hash the data in the url to avoid url length issues. - */ - useHash?: boolean; - - /** - * When `true` filters from saved filters from destination dashboard as merged with applied filters - * When `false` applied filters take precedence and override saved filters - * - * true is default - */ - preserveSavedFilters?: boolean; - - /** - * View mode of the dashboard. - */ - viewMode?: ViewMode; - - /** - * Search search session ID to restore. - * (Background search) - */ - searchSessionId?: string; - - /** - * List of dashboard panels - */ - panels?: SavedDashboardPanel[]; - - /** - * Saved query ID - */ - savedQuery?: string; -} - -/** - * @deprecated Use dashboard locator instead. - */ -export const createDashboardUrlGenerator = ( - getStartServices: () => Promise<{ - appBasePath: string; - useHashedUrl: boolean; - savedDashboardLoader: SavedObjectLoader; - }> -): UrlGeneratorsDefinition => ({ - id: DASHBOARD_APP_URL_GENERATOR, - createUrl: async (state) => { - const startServices = await getStartServices(); - const useHash = state.useHash ?? startServices.useHashedUrl; - const appBasePath = startServices.appBasePath; - const hash = state.dashboardId ? `view/${state.dashboardId}` : `create`; - - const getSavedFiltersFromDestinationDashboardIfNeeded = async (): Promise => { - if (state.preserveSavedFilters === false) return []; - if (!state.dashboardId) return []; - try { - const dashboard = await startServices.savedDashboardLoader.get(state.dashboardId); - return dashboard?.searchSource?.getField('filter') ?? []; - } catch (e) { - // in case dashboard is missing, built the url without those filters - // dashboard app will handle redirect to landing page with toast message - return []; - } - }; - - const cleanEmptyKeys = (stateObj: Record) => { - Object.keys(stateObj).forEach((key) => { - if (stateObj[key] === undefined) { - delete stateObj[key]; - } - }); - return stateObj; - }; - - // leave filters `undefined` if no filters was applied - // in this case dashboard will restore saved filters on its own - const filters = state.filters && [ - ...(await getSavedFiltersFromDestinationDashboardIfNeeded()), - ...state.filters, - ]; - - let url = setStateToKbnUrl( - STATE_STORAGE_KEY, - cleanEmptyKeys({ - query: state.query, - filters: filters?.filter((f) => !esFilters.isFilterPinned(f)), - viewMode: state.viewMode, - panels: state.panels, - savedQuery: state.savedQuery, - }), - { useHash }, - `${appBasePath}#/${hash}` - ); - - url = setStateToKbnUrl( - GLOBAL_STATE_STORAGE_KEY, - cleanEmptyKeys({ - time: state.timeRange, - filters: filters?.filter((f) => esFilters.isFilterPinned(f)), - refreshInterval: state.refreshInterval, - }), - { useHash }, - url - ); - - if (state.searchSessionId) { - url = `${url}&${DashboardConstants.SEARCH_SESSION_ID}=${state.searchSessionId}`; - } - - return url; - }, -}); diff --git a/src/plugins/dashboard/server/saved_objects/dashboard_migrations.ts b/src/plugins/dashboard/server/saved_objects/dashboard_migrations.ts index e0cd410ce5e8f..ed8f87ad9b51b 100644 --- a/src/plugins/dashboard/server/saved_objects/dashboard_migrations.ts +++ b/src/plugins/dashboard/server/saved_objects/dashboard_migrations.ts @@ -25,7 +25,7 @@ import { convertSavedDashboardPanelToPanelState, } from '../../common/embeddable/embeddable_saved_object_converters'; import { SavedObjectEmbeddableInput } from '../../../embeddable/common'; -import { INDEX_PATTERN_SAVED_OBJECT_TYPE } from '../../../data/common'; +import { DATA_VIEW_SAVED_OBJECT_TYPE } from '../../../data/common'; import { mergeMigrationFunctionMaps, MigrateFunction, @@ -49,7 +49,7 @@ function migrateIndexPattern(doc: DashboardDoc700To720) { searchSource.indexRefName = 'kibanaSavedObjectMeta.searchSourceJSON.index'; doc.references.push({ name: searchSource.indexRefName, - type: INDEX_PATTERN_SAVED_OBJECT_TYPE, + type: DATA_VIEW_SAVED_OBJECT_TYPE, id: searchSource.index, }); delete searchSource.index; @@ -62,7 +62,7 @@ function migrateIndexPattern(doc: DashboardDoc700To720) { filterRow.meta.indexRefName = `kibanaSavedObjectMeta.searchSourceJSON.filter[${i}].meta.index`; doc.references.push({ name: filterRow.meta.indexRefName, - type: INDEX_PATTERN_SAVED_OBJECT_TYPE, + type: DATA_VIEW_SAVED_OBJECT_TYPE, id: filterRow.meta.index, }); delete filterRow.meta.index; diff --git a/src/plugins/dashboard/server/saved_objects/move_filters_to_query.test.ts b/src/plugins/dashboard/server/saved_objects/move_filters_to_query.test.ts index 8980bd1903323..4000bed0c28ac 100644 --- a/src/plugins/dashboard/server/saved_objects/move_filters_to_query.test.ts +++ b/src/plugins/dashboard/server/saved_objects/move_filters_to_query.test.ts @@ -6,13 +6,13 @@ * Side Public License, v 1. */ -import { esFilters, Filter } from 'src/plugins/data/public'; +import { FilterStateStore, Filter } from '@kbn/es-query'; import { moveFiltersToQuery, Pre600FilterQuery } from './move_filters_to_query'; const filter: Filter = { meta: { disabled: false, negate: false, alias: '' }, query: {}, - $state: { store: esFilters.FilterStateStore.APP_STATE }, + $state: { store: FilterStateStore.APP_STATE }, }; const queryFilter: Pre600FilterQuery = { @@ -27,7 +27,7 @@ test('Migrates an old filter query into the query field', () => { expect(newSearchSource).toEqual({ filter: [ { - $state: { store: esFilters.FilterStateStore.APP_STATE }, + $state: { store: FilterStateStore.APP_STATE }, meta: { alias: '', disabled: false, diff --git a/src/plugins/dashboard/server/saved_objects/replace_index_pattern_reference.ts b/src/plugins/dashboard/server/saved_objects/replace_index_pattern_reference.ts index ddd1c45841b9c..e2ea076de7743 100644 --- a/src/plugins/dashboard/server/saved_objects/replace_index_pattern_reference.ts +++ b/src/plugins/dashboard/server/saved_objects/replace_index_pattern_reference.ts @@ -7,14 +7,14 @@ */ import type { SavedObjectMigrationFn } from 'kibana/server'; -import { INDEX_PATTERN_SAVED_OBJECT_TYPE } from '../../../data/common'; +import { DATA_VIEW_SAVED_OBJECT_TYPE } from '../../../data/common'; export const replaceIndexPatternReference: SavedObjectMigrationFn = (doc) => ({ ...doc, references: Array.isArray(doc.references) ? doc.references.map((reference) => { if (reference.type === 'index_pattern') { - reference.type = INDEX_PATTERN_SAVED_OBJECT_TYPE; + reference.type = DATA_VIEW_SAVED_OBJECT_TYPE; } return reference; }) diff --git a/src/plugins/dashboard/tsconfig.json b/src/plugins/dashboard/tsconfig.json index 680d06780543a..55049447aee57 100644 --- a/src/plugins/dashboard/tsconfig.json +++ b/src/plugins/dashboard/tsconfig.json @@ -11,6 +11,7 @@ { "path": "../../core/tsconfig.json" }, { "path": "../inspector/tsconfig.json" }, { "path": "../kibana_react/tsconfig.json" }, + { "path": "../data_views/tsconfig.json" }, { "path": "../kibana_utils/tsconfig.json" }, { "path": "../share/tsconfig.json" }, { "path": "../controls/tsconfig.json" }, diff --git a/src/plugins/data/common/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts b/src/plugins/data/common/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts index da110c1abb7e9..f8c903b8cfe42 100644 --- a/src/plugins/data/common/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts +++ b/src/plugins/data/common/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts @@ -30,7 +30,7 @@ const metricAggFilter: string[] = [ '!filtered_metric', '!single_percentile', ]; -const bucketAggFilter: string[] = []; +const bucketAggFilter: string[] = ['!filter', '!sampler', '!diversified_sampler', '!multi_terms']; export const siblingPipelineType = i18n.translate( 'data.search.aggs.metrics.siblingPipelineAggregationsSubtypeTitle', diff --git a/src/plugins/data/common/search/tabify/tabify.test.ts b/src/plugins/data/common/search/tabify/tabify.test.ts index 6cdf9a3547d48..3e1b856de4100 100644 --- a/src/plugins/data/common/search/tabify/tabify.test.ts +++ b/src/plugins/data/common/search/tabify/tabify.test.ts @@ -8,7 +8,7 @@ import { tabifyAggResponse } from './tabify'; import { IndexPattern } from '../..'; -import { AggConfigs, IAggConfig, IAggConfigs } from '../aggs'; +import { AggConfigs, BucketAggParam, IAggConfig, IAggConfigs } from '../aggs'; import { mockAggTypesRegistry } from '../aggs/test_helpers'; import { metricOnly, threeTermBuckets } from './fixtures/fake_hierarchical_data'; @@ -54,6 +54,42 @@ describe('tabifyAggResponse Integration', () => { expect(resp.columns[0]).toHaveProperty('name', aggConfigs.aggs[0].makeLabel()); }); + describe('scaleMetricValues performance check', () => { + beforeAll(() => { + typesRegistry.get('count').params.push({ + name: 'scaleMetricValues', + default: false, + write: () => {}, + advanced: true, + } as any as BucketAggParam); + }); + test('does not call write if scaleMetricValues is not set', () => { + const aggConfigs = createAggConfigs([{ type: 'count' } as any]); + + const writeMock = jest.fn(); + aggConfigs.getRequestAggs()[0].write = writeMock; + + tabifyAggResponse(aggConfigs, metricOnly, { + metricsAtAllLevels: true, + }); + expect(writeMock).not.toHaveBeenCalled(); + }); + + test('does call write if scaleMetricValues is set', () => { + const aggConfigs = createAggConfigs([ + { type: 'count', params: { scaleMetricValues: true } } as any, + ]); + + const writeMock = jest.fn(() => ({})); + aggConfigs.getRequestAggs()[0].write = writeMock; + + tabifyAggResponse(aggConfigs, metricOnly, { + metricsAtAllLevels: true, + }); + expect(writeMock).toHaveBeenCalled(); + }); + }); + describe('transforms a complex response', () => { let esResp: typeof threeTermBuckets; let aggConfigs: IAggConfigs; diff --git a/src/plugins/data/common/search/tabify/tabify.ts b/src/plugins/data/common/search/tabify/tabify.ts index 5b1247a8f1719..1bdca61d654f7 100644 --- a/src/plugins/data/common/search/tabify/tabify.ts +++ b/src/plugins/data/common/search/tabify/tabify.ts @@ -37,8 +37,10 @@ export function tabifyAggResponse( if (column) { const agg = column.aggConfig; - const aggInfo = agg.write(aggs); - aggScale *= aggInfo.metricScale || 1; + if (agg.getParam('scaleMetricValues')) { + const aggInfo = agg.write(aggs); + aggScale *= aggInfo.metricScale || 1; + } switch (agg.type.type) { case AggGroupNames.Buckets: diff --git a/src/plugins/data_view_editor/public/components/form_schema.test.ts b/src/plugins/data_view_editor/public/components/form_schema.test.ts new file mode 100644 index 0000000000000..b2e1f697843c6 --- /dev/null +++ b/src/plugins/data_view_editor/public/components/form_schema.test.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { singleAstriskValidator } from './form_schema'; +import { ValidationFuncArg } from '../shared_imports'; + +describe('validators', () => { + test('singleAstriskValidator should pass', async () => { + const result = singleAstriskValidator({ value: 'kibana*' } as ValidationFuncArg); + expect(result).toBeUndefined(); + }); + test('singleAstriskValidator should fail', async () => { + const result = singleAstriskValidator({ value: '*' } as ValidationFuncArg); + // returns error + expect(result).toBeDefined(); + }); +}); diff --git a/src/plugins/data_view_editor/public/components/form_schema.ts b/src/plugins/data_view_editor/public/components/form_schema.ts index a6df0c4206d2a..178fedda2de34 100644 --- a/src/plugins/data_view_editor/public/components/form_schema.ts +++ b/src/plugins/data_view_editor/public/components/form_schema.ts @@ -7,9 +7,21 @@ */ import { i18n } from '@kbn/i18n'; -import { fieldValidators } from '../shared_imports'; +import { fieldValidators, ValidationFunc } from '../shared_imports'; import { INDEX_PATTERN_TYPE } from '../types'; +export const singleAstriskValidator = ( + ...args: Parameters +): ReturnType => { + const [{ value, path }] = args; + + const message = i18n.translate('indexPatternEditor.validations.noSingleAstriskPattern', { + defaultMessage: "A single '*' is not an allowed index pattern", + }); + + return value === '*' ? { code: 'ERR_FIELD_MISSING', path, message } : undefined; +}; + export const schema = { title: { label: i18n.translate('indexPatternEditor.editor.form.titleLabel', { @@ -28,6 +40,9 @@ export const schema = { }) ), }, + { + validator: singleAstriskValidator, + }, ], }, timestampField: { diff --git a/src/plugins/data_view_editor/public/shared_imports.ts b/src/plugins/data_view_editor/public/shared_imports.ts index cca695bc9a95e..dd9b8ea2a0e41 100644 --- a/src/plugins/data_view_editor/public/shared_imports.ts +++ b/src/plugins/data_view_editor/public/shared_imports.ts @@ -28,6 +28,7 @@ export type { ValidationFunc, FieldConfig, ValidationConfig, + ValidationFuncArg, } from '../../es_ui_shared/static/forms/hook_form_lib'; export { useForm, diff --git a/src/plugins/data_view_management/public/components/edit_index_pattern/create_edit_field/create_edit_field.tsx b/src/plugins/data_view_management/public/components/edit_index_pattern/create_edit_field/create_edit_field.tsx index 0f41c08fbc6fe..2d8469975430b 100644 --- a/src/plugins/data_view_management/public/components/edit_index_pattern/create_edit_field/create_edit_field.tsx +++ b/src/plugins/data_view_management/public/components/edit_index_pattern/create_edit_field/create_edit_field.tsx @@ -70,7 +70,11 @@ export const CreateEditField = withRouter( if (spec) { return ( <> - + { - const { application, uiSettings, overlays, chrome, dataViews } = + const { uiSettings, overlays, chrome, dataViews } = useKibana().services; const [fields, setFields] = useState(indexPattern.getNonScriptedFields()); const [conflictedFields, setConflictedFields] = useState( @@ -143,15 +143,16 @@ export const EditIndexPattern = withRouter( const showTagsSection = Boolean(indexPattern.timeFieldName || (tags && tags.length > 0)); const kibana = useKibana(); const docsUrl = kibana.services.docLinks!.links.elasticsearch.mapping; - const userEditPermission = !!application?.capabilities?.indexPatterns?.save; + const userEditPermission = dataViews.getCanSaveSync(); return (
{showTagsSection && ( diff --git a/src/plugins/data_view_management/public/components/edit_index_pattern/index_header/index_header.tsx b/src/plugins/data_view_management/public/components/edit_index_pattern/index_header/index_header.tsx index b64aed5c0811c..e40ef6a7ddf2f 100644 --- a/src/plugins/data_view_management/public/components/edit_index_pattern/index_header/index_header.tsx +++ b/src/plugins/data_view_management/public/components/edit_index_pattern/index_header/index_header.tsx @@ -16,6 +16,7 @@ interface IndexHeaderProps { defaultIndex?: string; setDefault?: () => void; deleteIndexPatternClick?: () => void; + canSave: boolean; } const setDefaultAriaLabel = i18n.translate('indexPatternManagement.editDataView.setDefaultAria', { @@ -40,12 +41,13 @@ export const IndexHeader: React.FC = ({ setDefault, deleteIndexPatternClick, children, + canSave, }) => { return ( {indexPattern.title}} rightSideItems={[ - defaultIndex !== indexPattern.id && setDefault && ( + defaultIndex !== indexPattern.id && setDefault && canSave && ( = ({ /> ), - deleteIndexPatternClick && ( + canSave && (
`; @@ -178,6 +198,47 @@ exports[`IndexedFieldsTable should filter based on the query bar 1`] = ` }, ] } + openModal={[Function]} + theme={Object {}} + /> +
+`; + +exports[`IndexedFieldsTable should filter based on the schema filter 1`] = ` +
+ `; @@ -212,6 +273,8 @@ exports[`IndexedFieldsTable should filter based on the type filter 1`] = ` }, ] } + openModal={[Function]} + theme={Object {}} /> `; @@ -291,8 +354,28 @@ exports[`IndexedFieldsTable should render normally 1`] = ` "name": "amount", "type": "long", }, + Object { + "displayName": "runtime", + "excluded": false, + "format": "", + "hasRuntime": true, + "info": Array [], + "isMapped": false, + "isUserEditable": false, + "kbnType": "number", + "name": "runtime", + "runtimeField": Object { + "script": Object { + "source": "emit('Hello');", + }, + "type": "long", + }, + "type": "long", + }, ] } + openModal={[Function]} + theme={Object {}} /> `; diff --git a/src/plugins/data_view_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx b/src/plugins/data_view_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx index 4773dbff38a28..c7b92c227a5d9 100644 --- a/src/plugins/data_view_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx +++ b/src/plugins/data_view_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx @@ -11,6 +11,7 @@ import { shallow, ShallowWrapper } from 'enzyme'; import { DataViewField, DataView, DataViewType } from 'src/plugins/data_views/public'; import { IndexedFieldsTable } from './indexed_fields_table'; import { getFieldInfo } from '../../utils'; +import { RuntimeField } from 'src/plugins/data_views/common'; jest.mock('@elastic/eui', () => ({ EuiFlexGroup: 'eui-flex-group', @@ -67,11 +68,12 @@ const rollupIndexPattern = { } as unknown as DataView; const mockFieldToIndexPatternField = ( - spec: Record + spec: Record ) => { return new DataViewField(spec as unknown as DataViewField['spec']); }; +const runtimeField: RuntimeField = { type: 'long', script: { source: "emit('Hello');" } }; const fields = [ { name: 'Elastic', @@ -88,8 +90,19 @@ const fields = [ isUserEditable: true, }, { name: 'amount', displayName: 'amount', esTypes: ['long'], isUserEditable: true }, + { + name: 'runtime', + displayName: 'runtime', + runtimeField, + }, ].map(mockFieldToIndexPatternField); +const mockedServices = { + userEditPermission: false, + openModal: () => ({ onClose: new Promise(() => {}), close: async () => {} }), + theme: {} as any, +}; + describe('IndexedFieldsTable', () => { test('should render normally', async () => { const component: ShallowWrapper, React.Component<{}, {}, any>> = shallow( @@ -100,10 +113,12 @@ describe('IndexedFieldsTable', () => { fieldWildcardMatcher={() => { return () => false; }} - indexedFieldTypeFilter="" + indexedFieldTypeFilter={[]} + schemaFieldTypeFilter={[]} fieldFilter="" + {...mockedServices} /> - ).dive(); + ); await new Promise((resolve) => process.nextTick(resolve)); component.update(); @@ -120,10 +135,12 @@ describe('IndexedFieldsTable', () => { fieldWildcardMatcher={() => { return () => false; }} - indexedFieldTypeFilter="" + indexedFieldTypeFilter={[]} + schemaFieldTypeFilter={[]} fieldFilter="" + {...mockedServices} /> - ).dive(); + ); await new Promise((resolve) => process.nextTick(resolve)); component.setProps({ fieldFilter: 'Elast' }); @@ -141,13 +158,38 @@ describe('IndexedFieldsTable', () => { fieldWildcardMatcher={() => { return () => false; }} - indexedFieldTypeFilter="" + indexedFieldTypeFilter={[]} + schemaFieldTypeFilter={[]} + fieldFilter="" + {...mockedServices} + /> + ); + + await new Promise((resolve) => process.nextTick(resolve)); + component.setProps({ indexedFieldTypeFilter: ['date'] }); + component.update(); + + expect(component).toMatchSnapshot(); + }); + + test('should filter based on the schema filter', async () => { + const component: ShallowWrapper, React.Component<{}, {}, any>> = shallow( + { + return () => false; + }} + indexedFieldTypeFilter={[]} + schemaFieldTypeFilter={[]} fieldFilter="" + {...mockedServices} /> - ).dive(); + ); await new Promise((resolve) => process.nextTick(resolve)); - component.setProps({ indexedFieldTypeFilter: 'date' }); + component.setProps({ schemaFieldTypeFilter: ['runtime'] }); component.update(); expect(component).toMatchSnapshot(); @@ -163,10 +205,12 @@ describe('IndexedFieldsTable', () => { fieldWildcardMatcher={() => { return () => false; }} - indexedFieldTypeFilter="" + indexedFieldTypeFilter={[]} + schemaFieldTypeFilter={[]} fieldFilter="" + {...mockedServices} /> - ).dive(); + ); await new Promise((resolve) => process.nextTick(resolve)); component.update(); diff --git a/src/plugins/data_view_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx b/src/plugins/data_view_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx index 667a4e029e02b..ad85499009db0 100644 --- a/src/plugins/data_view_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx +++ b/src/plugins/data_view_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx @@ -10,16 +10,15 @@ import React, { Component } from 'react'; import { createSelector } from 'reselect'; import { OverlayStart, ThemeServiceStart } from 'src/core/public'; import { DataViewField, DataView } from '../../../../../../plugins/data_views/public'; -import { useKibana } from '../../../../../../plugins/kibana_react/public'; import { Table } from './components/table'; import { IndexedFieldItem } from './types'; -import { IndexPatternManagmentContext } from '../../../types'; interface IndexedFieldsTableProps { fields: DataViewField[]; indexPattern: DataView; fieldFilter?: string; - indexedFieldTypeFilter?: string; + indexedFieldTypeFilter: string[]; + schemaFieldTypeFilter: string[]; helpers: { editField: (fieldName: string) => void; deleteField: (fieldName: string) => void; @@ -35,16 +34,10 @@ interface IndexedFieldsTableState { fields: IndexedFieldItem[]; } -const withHooks = (Comp: typeof Component) => { - return (props: any) => { - const { application } = useKibana().services; - const userEditPermission = !!application?.capabilities?.indexPatterns?.save; - - return ; - }; -}; - -class IndexedFields extends Component { +export class IndexedFieldsTable extends Component< + IndexedFieldsTableProps, + IndexedFieldsTableState +> { constructor(props: IndexedFieldsTableProps) { super(props); @@ -93,7 +86,8 @@ class IndexedFields extends Component props.fieldFilter, (state: IndexedFieldsTableState, props: IndexedFieldsTableProps) => props.indexedFieldTypeFilter, - (fields, fieldFilter, indexedFieldTypeFilter) => { + (state: IndexedFieldsTableState, props: IndexedFieldsTableProps) => props.schemaFieldTypeFilter, + (fields, fieldFilter, indexedFieldTypeFilter, schemaFieldTypeFilter) => { if (fieldFilter) { const normalizedFieldFilter = fieldFilter.toLowerCase(); fields = fields.filter( @@ -103,14 +97,34 @@ class IndexedFields extends Component { - if (indexedFieldTypeFilter === 'conflict' && field.kbnType === 'conflict') { + if (indexedFieldTypeFilter.includes('conflict') && field.kbnType === 'conflict') { + return true; + } + if ( + 'runtimeField' in field && + field.runtimeField?.type && + indexedFieldTypeFilter.includes(field.runtimeField?.type) + ) { return true; } // match one of multiple types on a field - return field.esTypes?.length && field.esTypes?.indexOf(indexedFieldTypeFilter) !== -1; + return ( + field.esTypes?.length && + field.esTypes.filter((val) => indexedFieldTypeFilter.includes(val)).length + ); + }); + } + + if (schemaFieldTypeFilter.length) { + // match fields of schema type + fields = fields.filter((field) => { + return ( + (schemaFieldTypeFilter.includes('runtime') && 'runtimeField' in field) || + (schemaFieldTypeFilter.includes('indexed') && !('runtimeField' in field)) + ); }); } @@ -136,5 +150,3 @@ class IndexedFields extends Component { - const { application, docLinks } = useKibana().services; + const { dataViews, docLinks } = useKibana().services; const links = docLinks?.links; - const userEditPermission = !!application?.capabilities?.indexPatterns?.save; + const userEditPermission = dataViews.getCanSaveSync(); return ( diff --git a/src/plugins/data_view_management/public/components/edit_index_pattern/scripted_fields_table/scripted_field_table.test.tsx b/src/plugins/data_view_management/public/components/edit_index_pattern/scripted_fields_table/scripted_field_table.test.tsx index aa74ae8c78fae..4febfdf0e1219 100644 --- a/src/plugins/data_view_management/public/components/edit_index_pattern/scripted_fields_table/scripted_field_table.test.tsx +++ b/src/plugins/data_view_management/public/components/edit_index_pattern/scripted_fields_table/scripted_field_table.test.tsx @@ -68,8 +68,10 @@ describe('ScriptedFieldsTable', () => { helpers={helpers} painlessDocLink={'painlessDoc'} saveIndexPattern={async () => {}} + userEditPermission={false} + scriptedFieldLanguageFilter={[]} /> - ).dive(); + ); // Allow the componentWillMount code to execute // https://github.com/airbnb/enzyme/issues/450 @@ -86,8 +88,10 @@ describe('ScriptedFieldsTable', () => { helpers={helpers} painlessDocLink={'painlessDoc'} saveIndexPattern={async () => {}} + userEditPermission={false} + scriptedFieldLanguageFilter={[]} /> - ).dive(); + ); // Allow the componentWillMount code to execute // https://github.com/airbnb/enzyme/issues/450 @@ -117,15 +121,17 @@ describe('ScriptedFieldsTable', () => { painlessDocLink={'painlessDoc'} helpers={helpers} saveIndexPattern={async () => {}} + userEditPermission={false} + scriptedFieldLanguageFilter={[]} /> - ).dive(); + ); // Allow the componentWillMount code to execute // https://github.com/airbnb/enzyme/issues/450 await component.update(); // Fire `componentWillMount()` await component.update(); // Force update the component post async actions - component.setProps({ scriptedFieldLanguageFilter: 'painless' }); + component.setProps({ scriptedFieldLanguageFilter: ['painless'] }); component.update(); expect(component).toMatchSnapshot(); @@ -142,8 +148,10 @@ describe('ScriptedFieldsTable', () => { painlessDocLink={'painlessDoc'} helpers={helpers} saveIndexPattern={async () => {}} + userEditPermission={false} + scriptedFieldLanguageFilter={[]} /> - ).dive(); + ); // Allow the componentWillMount code to execute // https://github.com/airbnb/enzyme/issues/450 @@ -162,8 +170,10 @@ describe('ScriptedFieldsTable', () => { helpers={helpers} painlessDocLink={'painlessDoc'} saveIndexPattern={async () => {}} + userEditPermission={false} + scriptedFieldLanguageFilter={[]} /> - ).dive(); + ); await component.update(); // Fire `componentWillMount()` // @ts-expect-error lang is not valid @@ -189,8 +199,10 @@ describe('ScriptedFieldsTable', () => { helpers={helpers} painlessDocLink={'painlessDoc'} saveIndexPattern={async () => {}} + userEditPermission={false} + scriptedFieldLanguageFilter={[]} /> - ).dive(); + ); await component.update(); // Fire `componentWillMount()` // @ts-expect-error diff --git a/src/plugins/data_view_management/public/components/edit_index_pattern/scripted_fields_table/scripted_fields_table.tsx b/src/plugins/data_view_management/public/components/edit_index_pattern/scripted_fields_table/scripted_fields_table.tsx index 2e3657b23c331..540131c50b236 100644 --- a/src/plugins/data_view_management/public/components/edit_index_pattern/scripted_fields_table/scripted_fields_table.tsx +++ b/src/plugins/data_view_management/public/components/edit_index_pattern/scripted_fields_table/scripted_fields_table.tsx @@ -15,15 +15,13 @@ import { import { Table, Header, CallOuts, DeleteScritpedFieldConfirmationModal } from './components'; import { ScriptedFieldItem } from './types'; -import { IndexPatternManagmentContext } from '../../../types'; import { DataView, DataViewsPublicPluginStart } from '../../../../../../plugins/data_views/public'; -import { useKibana } from '../../../../../../plugins/kibana_react/public'; interface ScriptedFieldsTableProps { indexPattern: DataView; fieldFilter?: string; - scriptedFieldLanguageFilter?: string; + scriptedFieldLanguageFilter: string[]; helpers: { redirectToRoute: Function; getRouteHref?: Function; @@ -41,16 +39,10 @@ interface ScriptedFieldsTableState { fields: ScriptedFieldItem[]; } -const withHooks = (Comp: typeof Component) => { - return (props: any) => { - const { application } = useKibana().services; - const userEditPermission = !!application?.capabilities?.indexPatterns?.save; - - return ; - }; -}; - -class ScriptedFields extends Component { +export class ScriptedFieldsTable extends Component< + ScriptedFieldsTableProps, + ScriptedFieldsTableState +> { constructor(props: ScriptedFieldsTableProps) { super(props); @@ -92,9 +84,9 @@ class ScriptedFields extends Component field.lang === this.props.scriptedFieldLanguageFilter + if (scriptedFieldLanguageFilter.length) { + languageFilteredFields = fields.filter((field) => + scriptedFieldLanguageFilter.includes(field.lang) ); } @@ -168,5 +160,3 @@ class ScriptedFields extends Component { @@ -44,6 +47,12 @@ interface TabsProps extends Pick { refreshFields: () => void; } +interface FilterItems { + value: string; + name: string; + checked?: FilterChecked; +} + const searchAriaLabel = i18n.translate( 'indexPatternManagement.editIndexPattern.fields.searchAria', { @@ -51,6 +60,10 @@ const searchAriaLabel = i18n.translate( } ); +const filterLabel = i18n.translate('indexPatternManagement.editIndexPattern.fields.filter', { + defaultMessage: 'Field type', +}); + const filterAriaLabel = i18n.translate( 'indexPatternManagement.editIndexPattern.fields.filterAria', { @@ -58,6 +71,45 @@ const filterAriaLabel = i18n.translate( } ); +const schemaFilterLabel = i18n.translate('indexPatternManagement.editIndexPattern.fields.schema', { + defaultMessage: 'Schema type', +}); + +const schemaAriaLabel = i18n.translate( + 'indexPatternManagement.editIndexPattern.fields.schemaAria', + { + defaultMessage: 'Filter schema types', + } +); + +const scriptedFieldFilterLabel = i18n.translate( + 'indexPatternManagement.editIndexPattern.fields.scriptedFieldFilter', + { + defaultMessage: 'All languages', + } +); + +const scriptedFieldAriaLabel = i18n.translate( + 'indexPatternManagement.editIndexPattern.fields.scriptedFieldFilterAria', + { + defaultMessage: 'Filter scripted field languages', + } +); + +const schemaOptionRuntime = i18n.translate( + 'indexPatternManagement.editIndexPattern.fields.runtime', + { + defaultMessage: 'Runtime', + } +); + +const schemaOptionIndexed = i18n.translate( + 'indexPatternManagement.editIndexPattern.fields.indexed', + { + defaultMessage: 'Indexed', + } +); + const filterPlaceholder = i18n.translate( 'indexPatternManagement.editIndexPattern.fields.filterPlaceholder', { @@ -80,19 +132,56 @@ export function Tabs({ location, refreshFields, }: TabsProps) { - const { application, uiSettings, docLinks, dataViewFieldEditor, overlays, theme } = + const { uiSettings, docLinks, dataViewFieldEditor, overlays, theme, dataViews } = useKibana().services; const [fieldFilter, setFieldFilter] = useState(''); - const [indexedFieldTypeFilter, setIndexedFieldTypeFilter] = useState(''); - const [scriptedFieldLanguageFilter, setScriptedFieldLanguageFilter] = useState(''); - const [indexedFieldTypes, setIndexedFieldType] = useState([]); - const [scriptedFieldLanguages, setScriptedFieldLanguages] = useState([]); const [syncingStateFunc, setSyncingStateFunc] = useState({ getCurrentTab: () => TAB_INDEXED_FIELDS, }); + const [scriptedFieldLanguageFilter, setScriptedFieldLanguageFilter] = useState([]); + const [isScriptedFieldFilterOpen, setIsScriptedFieldFilterOpen] = useState(false); + const [scriptedFieldLanguages, setScriptedFieldLanguages] = useState([]); + const [indexedFieldTypeFilter, setIndexedFieldTypeFilter] = useState([]); + const [isIndexedFilterOpen, setIsIndexedFilterOpen] = useState(false); + const [indexedFieldTypes, setIndexedFieldTypes] = useState([]); + const [schemaFieldTypeFilter, setSchemaFieldTypeFilter] = useState([]); + const [isSchemaFilterOpen, setIsSchemaFilterOpen] = useState(false); + const [schemaItems, setSchemaItems] = useState([ + { + value: 'runtime', + name: schemaOptionRuntime, + }, + { + value: 'indexed', + name: schemaOptionIndexed, + }, + ]); const closeEditorHandler = useRef<() => void | undefined>(); const { DeleteRuntimeFieldProvider } = dataViewFieldEditor; + const updateFilterItem = ( + items: FilterItems[], + index: number, + updater: (a: FilterItems[]) => void + ) => { + if (!items[index]) { + return; + } + + const newItems = [...items]; + + switch (newItems[index].checked) { + case 'on': + newItems[index].checked = undefined; + break; + + default: + newItems[index].checked = 'on'; + } + + updater(newItems); + }; + const refreshFilters = useCallback(() => { const tempIndexedFieldTypes: string[] = []; const tempScriptedFieldLanguages: string[] = []; @@ -113,10 +202,8 @@ export function Tabs({ } }); - setIndexedFieldType(convertToEuiSelectOption(tempIndexedFieldTypes, 'indexedFiledTypes')); - setScriptedFieldLanguages( - convertToEuiSelectOption(tempScriptedFieldLanguages, 'scriptedFieldLanguages') - ); + setIndexedFieldTypes(convertToEuiFilterOptions(tempIndexedFieldTypes)); + setScriptedFieldLanguages(convertToEuiFilterOptions(tempScriptedFieldLanguages)); }, [indexPattern]); const closeFieldEditor = useCallback(() => { @@ -154,7 +241,7 @@ export function Tabs({ [uiSettings] ); - const userEditPermission = !!application?.capabilities?.indexPatterns?.save; + const userEditPermission = dataViews.getCanSaveSync(); const getFilterSection = useCallback( (type: string) => { return ( @@ -172,13 +259,92 @@ export function Tabs({ {type === TAB_INDEXED_FIELDS && indexedFieldTypes.length > 0 && ( <> - setIndexedFieldTypeFilter(e.target.value)} - data-test-subj="indexedFieldTypeFilterDropdown" - aria-label={filterAriaLabel} - /> + + setIsIndexedFilterOpen(!isIndexedFilterOpen)} + isSelected={isIndexedFilterOpen} + numFilters={indexedFieldTypes.length} + hasActiveFilters={!!indexedFieldTypes.find((item) => item.checked === 'on')} + numActiveFilters={ + indexedFieldTypes.filter((item) => item.checked === 'on').length + } + > + {filterLabel} + + } + isOpen={isIndexedFilterOpen} + closePopover={() => setIsIndexedFilterOpen(false)} + > + {indexedFieldTypes.map((item, index) => ( + { + setIndexedFieldTypeFilter( + item.checked + ? indexedFieldTypeFilter.filter((f) => f !== item.value) + : [...indexedFieldTypeFilter, item.value] + ); + updateFilterItem(indexedFieldTypes, index, setIndexedFieldTypes); + }} + data-test-subj={`indexedFieldTypeFilterDropdown-option-${item.value}${ + item.checked ? '-checked' : '' + }`} + > + {item.name} + + ))} + + setIsSchemaFilterOpen(!isSchemaFilterOpen)} + isSelected={isSchemaFilterOpen} + numFilters={schemaItems.length} + hasActiveFilters={!!schemaItems.find((item) => item.checked === 'on')} + numActiveFilters={ + schemaItems.filter((item) => item.checked === 'on').length + } + > + {schemaFilterLabel} + + } + isOpen={isSchemaFilterOpen} + closePopover={() => setIsSchemaFilterOpen(false)} + > + {schemaItems.map((item, index) => ( + { + setSchemaFieldTypeFilter( + item.checked + ? schemaFieldTypeFilter.filter((f) => f !== item.value) + : [...schemaFieldTypeFilter, item.value] + ); + updateFilterItem(schemaItems, index, setSchemaItems); + }} + data-test-subj={`schemaFieldTypeFilterDropdown-option-${item.value}${ + item.checked ? '-checked' : '' + }`} + > + {item.name} + + ))} + + {userEditPermission && ( @@ -191,12 +357,52 @@ export function Tabs({ )} {type === TAB_SCRIPTED_FIELDS && scriptedFieldLanguages.length > 0 && ( - setScriptedFieldLanguageFilter(e.target.value)} - data-test-subj="scriptedFieldLanguageFilterDropdown" - /> + + setIsScriptedFieldFilterOpen(!isScriptedFieldFilterOpen)} + isSelected={isScriptedFieldFilterOpen} + numFilters={scriptedFieldLanguages.length} + hasActiveFilters={ + !!scriptedFieldLanguages.find((item) => item.checked === 'on') + } + numActiveFilters={ + scriptedFieldLanguages.filter((item) => item.checked === 'on').length + } + > + {scriptedFieldFilterLabel} + + } + isOpen={isScriptedFieldFilterOpen} + closePopover={() => setIsScriptedFieldFilterOpen(false)} + > + {scriptedFieldLanguages.map((item, index) => ( + { + setScriptedFieldLanguageFilter( + item.checked + ? scriptedFieldLanguageFilter.filter((f) => f !== item.value) + : [...scriptedFieldLanguageFilter, item.value] + ); + updateFilterItem(scriptedFieldLanguages, index, setScriptedFieldLanguages); + }} + data-test-subj={`scriptedFieldLanguageFilterDropdown-option-${item.value}${ + item.checked ? '-checked' : '' + }`} + > + {item.name} + + ))} + + )} @@ -206,8 +412,13 @@ export function Tabs({ fieldFilter, indexedFieldTypeFilter, indexedFieldTypes, + isIndexedFilterOpen, scriptedFieldLanguageFilter, scriptedFieldLanguages, + isScriptedFieldFilterOpen, + schemaItems, + schemaFieldTypeFilter, + isSchemaFilterOpen, openFieldEditor, userEditPermission, ] @@ -230,13 +441,15 @@ export function Tabs({ fieldFilter={fieldFilter} fieldWildcardMatcher={fieldWildcardMatcherDecorated} indexedFieldTypeFilter={indexedFieldTypeFilter} + schemaFieldTypeFilter={schemaFieldTypeFilter} helpers={{ editField: openFieldEditor, deleteField, getFieldInfo, }} openModal={overlays.openModal} - theme={theme} + theme={theme!} + userEditPermission={dataViews.getCanSaveSync()} /> )} @@ -260,6 +473,7 @@ export function Tabs({ }} onRemoveField={refreshFilters} painlessDocLink={docLinks.links.scriptedFields.painless} + userEditPermission={dataViews.getCanSaveSync()} /> ); @@ -289,6 +503,7 @@ export function Tabs({ history, indexPattern, indexedFieldTypeFilter, + schemaFieldTypeFilter, refreshFilters, scriptedFieldLanguageFilter, saveIndexPattern, @@ -297,6 +512,7 @@ export function Tabs({ refreshFields, overlays, theme, + dataViews, ] ); diff --git a/src/plugins/data_view_management/public/components/edit_index_pattern/tabs/utils.ts b/src/plugins/data_view_management/public/components/edit_index_pattern/tabs/utils.ts index 0ea8d9d9e28f3..e82722b1a5127 100644 --- a/src/plugins/data_view_management/public/components/edit_index_pattern/tabs/utils.ts +++ b/src/plugins/data_view_management/public/components/edit_index_pattern/tabs/utils.ts @@ -105,36 +105,11 @@ export function getPath(field: DataViewField, indexPattern: DataView) { return `/dataView/${indexPattern?.id}/field/${encodeURIComponent(field.name)}`; } -const allTypesDropDown = i18n.translate( - 'indexPatternManagement.editIndexPattern.fields.allTypesDropDown', - { - defaultMessage: 'All field types', - } -); - -const allLangsDropDown = i18n.translate( - 'indexPatternManagement.editIndexPattern.fields.allLangsDropDown', - { - defaultMessage: 'All languages', - } -); - -export function convertToEuiSelectOption(options: string[], type: string) { - const euiOptions = - options.length > 0 - ? [ - { - value: '', - text: type === 'scriptedFieldLanguages' ? allLangsDropDown : allTypesDropDown, - }, - ] - : []; - return euiOptions.concat( - uniq(options).map((option) => { - return { - value: option, - text: option, - }; - }) - ); +export function convertToEuiFilterOptions(options: string[]) { + return uniq(options).map((option) => { + return { + value: option, + name: option, + }; + }); } diff --git a/src/plugins/data_view_management/public/management_app/mount_management_section.tsx b/src/plugins/data_view_management/public/management_app/mount_management_section.tsx index 1b876e34a42fb..e4978acbc9d17 100644 --- a/src/plugins/data_view_management/public/management_app/mount_management_section.tsx +++ b/src/plugins/data_view_management/public/management_app/mount_management_section.tsx @@ -39,7 +39,7 @@ export async function mountManagementSection( params: ManagementAppMountParams ) { const [ - { chrome, application, uiSettings, notifications, overlays, http, docLinks, theme }, + { chrome, uiSettings, notifications, overlays, http, docLinks, theme }, { data, dataViewFieldEditor, dataViewEditor, dataViews, fieldFormats }, indexPatternManagementStart, ] = await getStartServices(); @@ -51,7 +51,6 @@ export async function mountManagementSection( const deps: IndexPatternManagmentContext = { chrome, - application, uiSettings, notifications, overlays, diff --git a/src/plugins/data_view_management/public/mocks.ts b/src/plugins/data_view_management/public/mocks.ts index 3404ca4912c88..54c1900d37f4c 100644 --- a/src/plugins/data_view_management/public/mocks.ts +++ b/src/plugins/data_view_management/public/mocks.ts @@ -13,6 +13,7 @@ import { urlForwardingPluginMock } from '../../url_forwarding/public/mocks'; import { dataPluginMock } from '../../data/public/mocks'; import { indexPatternFieldEditorPluginMock } from '../../data_view_field_editor/public/mocks'; import { indexPatternEditorPluginMock } from '../../data_view_editor/public/mocks'; +import { dataViewPluginMocks } from '../../data_views/public/mocks'; import { IndexPatternManagementSetup, IndexPatternManagementStart, @@ -54,15 +55,14 @@ const docLinks = { const createIndexPatternManagmentContext = (): { [key in keyof IndexPatternManagmentContext]: any; } => { - const { chrome, application, uiSettings, notifications, overlays } = coreMock.createStart(); + const { chrome, uiSettings, notifications, overlays } = coreMock.createStart(); const { http } = coreMock.createSetup(); const data = dataPluginMock.createStartContract(); const dataViewFieldEditor = indexPatternFieldEditorPluginMock.createStartContract(); - const dataViews = data.indexPatterns; + const dataViews = dataViewPluginMocks.createStartContract(); return { chrome, - application, uiSettings, notifications, overlays, diff --git a/src/plugins/data_view_management/public/types.ts b/src/plugins/data_view_management/public/types.ts index dc5e0198a64f1..f0a79416892ef 100644 --- a/src/plugins/data_view_management/public/types.ts +++ b/src/plugins/data_view_management/public/types.ts @@ -8,7 +8,6 @@ import { ChromeStart, - ApplicationStart, IUiSettingsClient, OverlayStart, NotificationsStart, @@ -26,7 +25,6 @@ import { FieldFormatsStart } from '../../field_formats/public'; export interface IndexPatternManagmentContext { chrome: ChromeStart; - application: ApplicationStart; uiSettings: IUiSettingsClient; notifications: NotificationsStart; overlays: OverlayStart; diff --git a/src/plugins/data_views/public/mocks.ts b/src/plugins/data_views/public/mocks.ts index c9aece61c4e02..61713c9406c23 100644 --- a/src/plugins/data_views/public/mocks.ts +++ b/src/plugins/data_views/public/mocks.ts @@ -27,6 +27,7 @@ const createStartContract = (): Start => { }), get: jest.fn().mockReturnValue(Promise.resolve({})), clearCache: jest.fn(), + getCanSaveSync: jest.fn(), } as unknown as jest.Mocked; }; diff --git a/src/plugins/discover/public/utils/get_sharing_data.test.ts b/src/plugins/discover/public/utils/get_sharing_data.test.ts index aef9bcff15403..cc37599ef12c0 100644 --- a/src/plugins/discover/public/utils/get_sharing_data.test.ts +++ b/src/plugins/discover/public/utils/get_sharing_data.test.ts @@ -11,7 +11,11 @@ import type { DataView } from 'src/plugins/data/common'; import type { DiscoverServices } from '../build_services'; import { dataPluginMock } from '../../../data/public/mocks'; import { createSearchSourceMock } from '../../../data/common/search/search_source/mocks'; -import { DOC_HIDE_TIME_COLUMN_SETTING, SORT_DEFAULT_ORDER_SETTING } from '../../common'; +import { + DOC_HIDE_TIME_COLUMN_SETTING, + SORT_DEFAULT_ORDER_SETTING, + SEARCH_FIELDS_FROM_SOURCE, +} from '../../common'; import { indexPatternMock } from '../__mocks__/index_pattern'; import { getSharingData, showPublicUrlSwitch } from './get_sharing_data'; @@ -23,6 +27,9 @@ describe('getSharingData', () => { data: dataPluginMock.createStartContract(), uiSettings: { get: (key: string) => { + if (key === SEARCH_FIELDS_FROM_SOURCE) { + return false; + } if (key === SORT_DEFAULT_ORDER_SETTING) { return 'desc'; } @@ -64,6 +71,91 @@ describe('getSharingData', () => { `); }); + test('getSearchSource does not add fields to the searchSource', async () => { + const index = { ...indexPatternMock } as DataView; + index.timeFieldName = 'cool-timefield'; + const searchSourceMock = createSearchSourceMock({ index }); + const { getSearchSource } = await getSharingData(searchSourceMock, {}, services); + expect(getSearchSource()).toMatchInlineSnapshot(` + Object { + "index": "the-index-pattern-id", + "sort": Array [ + Object { + "_doc": "desc", + }, + ], + } + `); + }); + + test(`getSearchSource does not add fields to the searchSource with 'discover:searchFieldsFromSource=true'`, async () => { + const originalGet = services.uiSettings.get; + services.uiSettings = { + get: (key: string, ...args: unknown[]) => { + if (key === SEARCH_FIELDS_FROM_SOURCE) { + return true; + } + return originalGet(key, ...args); + }, + } as unknown as IUiSettingsClient; + const index = { ...indexPatternMock } as DataView; + index.timeFieldName = 'cool-timefield'; + const searchSourceMock = createSearchSourceMock({ index }); + const { getSearchSource } = await getSharingData( + searchSourceMock, + { + columns: [ + 'cool-field-1', + 'cool-field-2', + 'cool-field-3', + 'cool-field-4', + 'cool-field-5', + 'cool-field-6', + ], + }, + services + ); + expect(getSearchSource()).toMatchInlineSnapshot(` + Object { + "index": "the-index-pattern-id", + "sort": Array [ + Object { + "_doc": "desc", + }, + ], + } + `); + }); + + test('getSearchSource does add fields to the searchSource when columns are selected', async () => { + const index = { ...indexPatternMock } as DataView; + index.timeFieldName = 'cool-timefield'; + const searchSourceMock = createSearchSourceMock({ index }); + const { getSearchSource } = await getSharingData( + searchSourceMock, + { + columns: [ + 'cool-field-1', + 'cool-field-2', + 'cool-field-3', + 'cool-field-4', + 'cool-field-5', + 'cool-field-6', + ], + }, + services + ); + expect(getSearchSource().fields).toStrictEqual([ + 'cool-timefield', + 'cool-field-1', + 'cool-field-2', + 'cool-field-3', + 'cool-field-4', + 'cool-field-5', + 'cool-field-6', + ]); + }); + test('fields have prepended timeField', async () => { const index = { ...indexPatternMock } as DataView; index.timeFieldName = 'cool-timefield'; diff --git a/src/plugins/discover/public/utils/get_sharing_data.ts b/src/plugins/discover/public/utils/get_sharing_data.ts index e14ae252da95e..cd00fc5e3c70e 100644 --- a/src/plugins/discover/public/utils/get_sharing_data.ts +++ b/src/plugins/discover/public/utils/get_sharing_data.ts @@ -10,7 +10,11 @@ import type { Capabilities } from 'kibana/public'; import type { IUiSettingsClient } from 'kibana/public'; import type { DataPublicPluginStart } from 'src/plugins/data/public'; import type { Filter, ISearchSource, SerializedSearchSourceFields } from 'src/plugins/data/common'; -import { DOC_HIDE_TIME_COLUMN_SETTING, SORT_DEFAULT_ORDER_SETTING } from '../../common'; +import { + DOC_HIDE_TIME_COLUMN_SETTING, + SEARCH_FIELDS_FROM_SOURCE, + SORT_DEFAULT_ORDER_SETTING, +} from '../../common'; import type { SavedSearch, SortOrder } from '../services/saved_searches'; import { getSortForSearchSource } from '../components/doc_table'; import { AppState } from '../application/main/services/discover_state'; @@ -72,6 +76,15 @@ export async function getSharingData( searchSource.setField('filter', filter); } + /* + * For downstream querying performance, the searchSource object must have fields set. + * Otherwise, the requests will ask for all fields, even if only a few are really needed. + * Discover does not set fields, since having all fields is needed for the UI. + */ + const useFieldsApi = !config.get(SEARCH_FIELDS_FROM_SOURCE); + if (useFieldsApi && columns.length) { + searchSource.setField('fields', columns); + } return searchSource.getSerializedFields(true); }, columns, diff --git a/src/plugins/home/public/application/components/__snapshots__/home.test.tsx.snap b/src/plugins/home/public/application/components/__snapshots__/home.test.tsx.snap index 373fc8ea59b6f..ab6ad1b6cc0c5 100644 --- a/src/plugins/home/public/application/components/__snapshots__/home.test.tsx.snap +++ b/src/plugins/home/public/application/components/__snapshots__/home.test.tsx.snap @@ -396,6 +396,7 @@ exports[`home isNewKibanaInstance should set isNewKibanaInstance to true when th }, "delete": [MockFunction], "externalUrl": Object { + "isInternalUrl": [MockFunction], "validateUrl": [MockFunction], }, "fetch": [MockFunction], @@ -464,6 +465,7 @@ exports[`home isNewKibanaInstance should set isNewKibanaInstance to true when th }, "delete": [MockFunction], "externalUrl": Object { + "isInternalUrl": [MockFunction], "validateUrl": [MockFunction], }, "fetch": [MockFunction], @@ -533,6 +535,7 @@ exports[`home isNewKibanaInstance should set isNewKibanaInstance to true when th }, "delete": [MockFunction], "externalUrl": Object { + "isInternalUrl": [MockFunction], "validateUrl": [MockFunction], }, "fetch": [MockFunction], diff --git a/src/plugins/kibana_react/public/code_editor/__snapshots__/code_editor.test.tsx.snap b/src/plugins/kibana_react/public/code_editor/__snapshots__/code_editor.test.tsx.snap index b05abbcece0b9..9a4511f8b03f5 100644 --- a/src/plugins/kibana_react/public/code_editor/__snapshots__/code_editor.test.tsx.snap +++ b/src/plugins/kibana_react/public/code_editor/__snapshots__/code_editor.test.tsx.snap @@ -126,6 +126,7 @@ exports[` is rendered 1`] = ` >
is rendered 1`] = ` /> - + -
-
-